<commit_before>// Copyright 2016 Hajime Hoshi
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package graphics

import (
	"errors"
	"fmt"
	"image"
	"image/color"
	"math"
	"sync"

	"github.com/hajimehoshi/ebiten/internal/affine"
	"github.com/hajimehoshi/ebiten/internal/opengl"
)

type command interface {
	Exec(context *opengl.Context, indexOffsetInBytes int) error
}

type commandQueue struct {
	commands    []command
	vertices    []float32
	verticesNum int
	m           sync.Mutex
}

var theCommandQueue = &commandQueue{
	commands: []command{},
	vertices: []float32{},
}

func (q *commandQueue) appendVertices(vertices []float32) {
	if len(q.vertices) < q.verticesNum+len(vertices) {
		n := q.verticesNum + len(vertices) - len(q.vertices)
		q.vertices = append(q.vertices, make([]float32, n)...)
	}
	copy(q.vertices[q.verticesNum:q.verticesNum+len(vertices)], vertices)
	q.verticesNum += len(vertices)
}

func (q *commandQueue) EnqueueDrawImageCommand(dst, src *Image, vertices []float32, clr affine.ColorM, mode opengl.CompositeMode) {
	q.m.Lock()
	defer q.m.Unlock()
	q.appendVertices(vertices)
	if 0 < len(q.commands) {
		if c, ok := q.commands[len(q.commands)-1].(*drawImageCommand); ok {
			if c.isMergeable(dst, src, clr, mode) {
				c.verticesNum += len(vertices)
				return
			}
		}
	}
	c := &drawImageCommand{
		dst:         dst,
		src:         src,
		verticesNum: len(vertices),
		color:       clr,
		mode:        mode,
	}
	q.commands = append(q.commands, c)
}

func (q *commandQueue) Enqueue(command command) {
	q.m.Lock()
	defer q.m.Unlock()
	q.commands = append(q.commands, command)
}

// commandGroups separates q.commands into some groups.
// The number of quads of drawImageCommand in one groups must be equal to or less than
// its limit (maxQuads).
func (q *commandQueue) commandGroups() [][]command {
	cs := q.commands
	gs := [][]command{}
	quads := 0
	for 0 < len(cs) {
		if len(gs) == 0 {
			gs = append(gs, []command{})
		}
		c := cs[0]
		switch c := c.(type) {
		case *drawImageCommand:
			if maxQuads >= quads+c.quadsNum() {
				quads += c.quadsNum()
				break
			}
			cc := c.split(maxQuads - quads)
			gs[len(gs)-1] = append(gs[len(gs)-1], cc[0])
			cs[0] = cc[1]
			quads = 0
			gs = append(gs, []command{})
			continue
		}
		gs[len(gs)-1] = append(gs[len(gs)-1], c)
		cs = cs[1:]
	}
	return gs
}

func (q *commandQueue) Flush(context *opengl.Context) error {
	q.m.Lock()
	defer q.m.Unlock()
	// glViewport must be called at least at every frame on iOS.
	context.ResetViewportSize()
	n := 0
	lastN := 0
	for _, g := range q.commandGroups() {
		for _, c := range g {
			switch c := c.(type) {
			case *drawImageCommand:
				n += c.verticesNum
			}
		}
		if 0 < n-lastN {
			context.BufferSubData(opengl.ArrayBuffer, q.vertices[lastN:n])
		}
		// NOTE: WebGL doesn't seem to have Check gl.MAX_ELEMENTS_VERTICES or gl.MAX_ELEMENTS_INDICES so far.
		// Let's use them to compare to len(quads) in the future.
		if maxQuads < (n-lastN)*opengl.Float.SizeInBytes()/QuadVertexSizeInBytes() {
			return fmt.Errorf("len(quads) must be equal to or less than %d", maxQuads)
		}
		numc := len(g)
		indexOffsetInBytes := 0
		for _, c := range g {
			if err := c.Exec(context, indexOffsetInBytes); err != nil {
				return err
			}
			if c, ok := c.(*drawImageCommand); ok {
				n := c.verticesNum * opengl.Float.SizeInBytes() / QuadVertexSizeInBytes()
				indexOffsetInBytes += 6 * n * 2
			}
		}
		if 0 < numc {
			// Call glFlush to prevent black flicking (especially on Android (#226) and iOS).
			context.Flush()
		}
		lastN = n
	}
	q.commands = []command{}
	q.verticesNum = 0
	return nil
}

func FlushCommands(context *opengl.Context) error {
	return theCommandQueue.Flush(context)
}

type fillCommand struct {
	dst   *Image
	color color.RGBA
}

func (c *fillCommand) Exec(context *opengl.Context, indexOffsetInBytes int) error {
	f, err := c.dst.createFramebufferIfNeeded(context)
	if err != nil {
		return err
	}
	if err := f.setAsViewport(context); err != nil {
		return err
	}
	cr, cg, cb, ca := c.color.R, c.color.G, c.color.B, c.color.A
	const max = math.MaxUint8
	r := float64(cr) / max
	g := float64(cg) / max
	b := float64(cb) / max
	a := float64(ca) / max
	return context.FillFramebuffer(r, g, b, a)
}

type drawImageCommand struct {
	dst         *Image
	src         *Image
	verticesNum int
	color       affine.ColorM
	mode        opengl.CompositeMode
}

func QuadVertexSizeInBytes() int {
	return 4 * theArrayBufferLayout.totalBytes()
}

func (c *drawImageCommand) Exec(context *opengl.Context, indexOffsetInBytes int) error {
	f, err := c.dst.createFramebufferIfNeeded(context)
	if err != nil {
		return err
	}
	if err := f.setAsViewport(context); err != nil {
		return err
	}
	context.BlendFunc(c.mode)

	n := c.quadsNum()
	if n == 0 {
		return nil
	}
	_, h := c.dst.Size()
	proj := f.projectionMatrix(h)
	p := &programContext{
		state:            &theOpenGLState,
		program:          theOpenGLState.programTexture,
		context:          context,
		projectionMatrix: proj,
		texture:          c.src.texture.native,
		colorM:           c.color,
	}
	if err := p.begin(); err != nil {
		return err
	}
	// TODO: We should call glBindBuffer here?
	// The buffer is already bound at begin() but it is counterintuitive.
	context.DrawElements(opengl.Triangles, 6*n, indexOffsetInBytes)
	return nil
}

func (c *drawImageCommand) split(quadsNum int) [2]*drawImageCommand {
	c1 := *c
	c2 := *c
	s := opengl.Float.SizeInBytes()
	n := quadsNum * QuadVertexSizeInBytes() / s
	c1.verticesNum = n
	c2.verticesNum -= n
	return [2]*drawImageCommand{&c1, &c2}
}

func (c *drawImageCommand) isMergeable(dst, src *Image, clr affine.ColorM, mode opengl.CompositeMode) bool {
	if c.dst != dst {
		return false
	}
	if c.src != src {
		return false
	}
	if !c.color.Equals(&clr) {
		return false
	}
	if c.mode != mode {
		return false
	}
	return true
}

func (c *drawImageCommand) quadsNum() int {
	return c.verticesNum * opengl.Float.SizeInBytes() / QuadVertexSizeInBytes()
}

type replacePixelsCommand struct {
	dst    *Image
	pixels []uint8
}

func (c *replacePixelsCommand) Exec(context *opengl.Context, indexOffsetInBytes int) error {
	f, err := c.dst.createFramebufferIfNeeded(context)
	if err != nil {
		return err
	}
	if err := f.setAsViewport(context); err != nil {
		return err
	}
	// Filling with non black or white color is required here for glTexSubImage2D.
	// Very mysterious but this actually works (Issue #186).
	// This is needed even after fixing a shader bug at f537378f2a6a8ef56e1acf1c03034967b77c7b51.
	if err := context.FillFramebuffer(0, 0, 0.5, 1); err != nil {
		return err
	}
	// This is necessary on Android. We can't call glClear just before glTexSubImage2D without
	// glFlush. glTexSubImage2D didn't work without this hack at least on Nexus 5x (#211).
	// This also happens when a fillCommand precedes a replacePixelsCommand.
	// TODO: Can we have a better way like optimizing commands?
	context.Flush()
	if err := context.BindTexture(c.dst.texture.native); err != nil {
		return err
	}
	context.TexSubImage2D(c.pixels, NextPowerOf2Int(c.dst.width), NextPowerOf2Int(c.dst.height))
	return nil
}

type disposeCommand struct {
	target *Image
}

func (c *disposeCommand) Exec(context *opengl.Context, indexOffsetInBytes int) error {
	if c.target.framebuffer != nil {
		context.DeleteFramebuffer(c.target.framebuffer.native)
	}
	if c.target.texture != nil {
		context.DeleteTexture(c.target.texture.native)
	}
	return nil
}

type newImageFromImageCommand struct {
	result *Image
	img    *image.RGBA
	filter opengl.Filter
}

func (c *newImageFromImageCommand) Exec(context *opengl.Context, indexOffsetInBytes int) error {
	origSize := c.img.Bounds().Size()
	if origSize.X < 1 {
		return errors.New("graphics: width must be equal or more than 1.")
	}
	if origSize.Y < 1 {
		return errors.New("graphics: height must be equal or more than 1.")
	}
	w, h := c.img.Bounds().Size().X, c.img.Bounds().Size().Y
	if c.img.Bounds() != image.Rect(0, 0, NextPowerOf2Int(w), NextPowerOf2Int(h)) {
		panic(fmt.Sprintf("graphics: invalid image bounds: %v", c.img.Bounds()))
	}
	native, err := context.NewTexture(w, h, c.img.Pix, c.filter)
	if err != nil {
		return err
	}
	c.result.texture = &texture{
		native: native,
	}
	return nil
}

type newImageCommand struct {
	result *Image
	width  int
	height int
	filter opengl.Filter
}

func (c *newImageCommand) Exec(context *opengl.Context, indexOffsetInBytes int) error {
	w := NextPowerOf2Int(c.width)
	h := NextPowerOf2Int(c.height)
	if w < 1 {
		return errors.New("graphics: width must be equal or more than 1.")
	}
	if h < 1 {
		return errors.New("graphics: height must be equal or more than 1.")
	}
	native, err := context.NewTexture(w, h, nil, c.filter)
	if err != nil {
		return err
	}
	c.result.texture = &texture{
		native: native,
	}
	return nil
}

type newScreenFramebufferImageCommand struct {
	result *Image
	width  int
	height int
}

func (c *newScreenFramebufferImageCommand) Exec(context *opengl.Context, indexOffsetInBytes int) error {
	if c.width < 1 {
		return errors.New("graphics: width must be equal or more than 1.")
	}
	if c.height < 1 {
		return errors.New("graphics: height must be equal or more than 1.")
	}
	f := &framebuffer{
		native: context.ScreenFramebuffer(),
		flipY:  true,
	}
	c.result.framebuffer = f
	return nil
}
<commit_msg>graphics: Refactoring: remove unnecessary empty slices<commit_after>// Copyright 2016 Hajime Hoshi
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package graphics

import (
	"errors"
	"fmt"
	"image"
	"image/color"
	"math"
	"sync"

	"github.com/hajimehoshi/ebiten/internal/affine"
	"github.com/hajimehoshi/ebiten/internal/opengl"
)

type command interface {
	Exec(context *opengl.Context, indexOffsetInBytes int) error
}

type commandQueue struct {
	commands    []command
	vertices    []float32
	verticesNum int
	m           sync.Mutex
}

var theCommandQueue = &commandQueue{}

func (q *commandQueue) appendVertices(vertices []float32) {
	if len(q.vertices) < q.verticesNum+len(vertices) {
		n := q.verticesNum + len(vertices) - len(q.vertices)
		q.vertices = append(q.vertices, make([]float32, n)...)
	}
	copy(q.vertices[q.verticesNum:q.verticesNum+len(vertices)], vertices)
	q.verticesNum += len(vertices)
}

func (q *commandQueue) EnqueueDrawImageCommand(dst, src *Image, vertices []float32, clr affine.ColorM, mode opengl.CompositeMode) {
	q.m.Lock()
	defer q.m.Unlock()
	q.appendVertices(vertices)
	if 0 < len(q.commands) {
		if c, ok := q.commands[len(q.commands)-1].(*drawImageCommand); ok {
			if c.isMergeable(dst, src, clr, mode) {
				c.verticesNum += len(vertices)
				return
			}
		}
	}
	c := &drawImageCommand{
		dst:         dst,
		src:         src,
		verticesNum: len(vertices),
		color:       clr,
		mode:        mode,
	}
	q.commands = append(q.commands, c)
}

func (q *commandQueue) Enqueue(command command) {
	q.m.Lock()
	defer q.m.Unlock()
	q.commands = append(q.commands, command)
}

// commandGroups separates q.commands into some groups.
// The number of quads of drawImageCommand in one groups must be equal to or less than
// its limit (maxQuads).
func (q *commandQueue) commandGroups() [][]command {
	cs := q.commands
	var gs [][]command
	quads := 0
	for 0 < len(cs) {
		if len(gs) == 0 {
			gs = append(gs, []command{})
		}
		c := cs[0]
		switch c := c.(type) {
		case *drawImageCommand:
			if maxQuads >= quads+c.quadsNum() {
				quads += c.quadsNum()
				break
			}
			cc := c.split(maxQuads - quads)
			gs[len(gs)-1] = append(gs[len(gs)-1], cc[0])
			cs[0] = cc[1]
			quads = 0
			gs = append(gs, []command{})
			continue
		}
		gs[len(gs)-1] = append(gs[len(gs)-1], c)
		cs = cs[1:]
	}
	return gs
}

func (q *commandQueue) Flush(context *opengl.Context) error {
	q.m.Lock()
	defer q.m.Unlock()
	// glViewport must be called at least at every frame on iOS.
	context.ResetViewportSize()
	n := 0
	lastN := 0
	for _, g := range q.commandGroups() {
		for _, c := range g {
			switch c := c.(type) {
			case *drawImageCommand:
				n += c.verticesNum
			}
		}
		if 0 < n-lastN {
			context.BufferSubData(opengl.ArrayBuffer, q.vertices[lastN:n])
		}
		// NOTE: WebGL doesn't seem to have Check gl.MAX_ELEMENTS_VERTICES or gl.MAX_ELEMENTS_INDICES so far.
		// Let's use them to compare to len(quads) in the future.
		if maxQuads < (n-lastN)*opengl.Float.SizeInBytes()/QuadVertexSizeInBytes() {
			return fmt.Errorf("len(quads) must be equal to or less than %d", maxQuads)
		}
		numc := len(g)
		indexOffsetInBytes := 0
		for _, c := range g {
			if err := c.Exec(context, indexOffsetInBytes); err != nil {
				return err
			}
			if c, ok := c.(*drawImageCommand); ok {
				n := c.verticesNum * opengl.Float.SizeInBytes() / QuadVertexSizeInBytes()
				indexOffsetInBytes += 6 * n * 2
			}
		}
		if 0 < numc {
			// Call glFlush to prevent black flicking (especially on Android (#226) and iOS).
			context.Flush()
		}
		lastN = n
	}
	q.commands = nil
	q.verticesNum = 0
	return nil
}

func FlushCommands(context *opengl.Context) error {
	return theCommandQueue.Flush(context)
}

type fillCommand struct {
	dst   *Image
	color color.RGBA
}

func (c *fillCommand) Exec(context *opengl.Context, indexOffsetInBytes int) error {
	f, err := c.dst.createFramebufferIfNeeded(context)
	if err != nil {
		return err
	}
	if err := f.setAsViewport(context); err != nil {
		return err
	}
	cr, cg, cb, ca := c.color.R, c.color.G, c.color.B, c.color.A
	const max = math.MaxUint8
	r := float64(cr) / max
	g := float64(cg) / max
	b := float64(cb) / max
	a := float64(ca) / max
	return context.FillFramebuffer(r, g, b, a)
}

type drawImageCommand struct {
	dst         *Image
	src         *Image
	verticesNum int
	color       affine.ColorM
	mode        opengl.CompositeMode
}

func QuadVertexSizeInBytes() int {
	return 4 * theArrayBufferLayout.totalBytes()
}

func (c *drawImageCommand) Exec(context *opengl.Context, indexOffsetInBytes int) error {
	f, err := c.dst.createFramebufferIfNeeded(context)
	if err != nil {
		return err
	}
	if err := f.setAsViewport(context); err != nil {
		return err
	}
	context.BlendFunc(c.mode)

	n := c.quadsNum()
	if n == 0 {
		return nil
	}
	_, h := c.dst.Size()
	proj := f.projectionMatrix(h)
	p := &programContext{
		state:            &theOpenGLState,
		program:          theOpenGLState.programTexture,
		context:          context,
		projectionMatrix: proj,
		texture:          c.src.texture.native,
		colorM:           c.color,
	}
	if err := p.begin(); err != nil {
		return err
	}
	// TODO: We should call glBindBuffer here?
	// The buffer is already bound at begin() but it is counterintuitive.
	context.DrawElements(opengl.Triangles, 6*n, indexOffsetInBytes)
	return nil
}

func (c *drawImageCommand) split(quadsNum int) [2]*drawImageCommand {
	c1 := *c
	c2 := *c
	s := opengl.Float.SizeInBytes()
	n := quadsNum * QuadVertexSizeInBytes() / s
	c1.verticesNum = n
	c2.verticesNum -= n
	return [2]*drawImageCommand{&c1, &c2}
}

func (c *drawImageCommand) isMergeable(dst, src *Image, clr affine.ColorM, mode opengl.CompositeMode) bool {
	if c.dst != dst {
		return false
	}
	if c.src != src {
		return false
	}
	if !c.color.Equals(&clr) {
		return false
	}
	if c.mode != mode {
		return false
	}
	return true
}

func (c *drawImageCommand) quadsNum() int {
	return c.verticesNum * opengl.Float.SizeInBytes() / QuadVertexSizeInBytes()
}

type replacePixelsCommand struct {
	dst    *Image
	pixels []uint8
}

func (c *replacePixelsCommand) Exec(context *opengl.Context, indexOffsetInBytes int) error {
	f, err := c.dst.createFramebufferIfNeeded(context)
	if err != nil {
		return err
	}
	if err := f.setAsViewport(context); err != nil {
		return err
	}
	// Filling with non black or white color is required here for glTexSubImage2D.
	// Very mysterious but this actually works (Issue #186).
	// This is needed even after fixing a shader bug at f537378f2a6a8ef56e1acf1c03034967b77c7b51.
	if err := context.FillFramebuffer(0, 0, 0.5, 1); err != nil {
		return err
	}
	// This is necessary on Android. We can't call glClear just before glTexSubImage2D without
	// glFlush. glTexSubImage2D didn't work without this hack at least on Nexus 5x (#211).
	// This also happens when a fillCommand precedes a replacePixelsCommand.
	// TODO: Can we have a better way like optimizing commands?
	context.Flush()
	if err := context.BindTexture(c.dst.texture.native); err != nil {
		return err
	}
	context.TexSubImage2D(c.pixels, NextPowerOf2Int(c.dst.width), NextPowerOf2Int(c.dst.height))
	return nil
}

type disposeCommand struct {
	target *Image
}

func (c *disposeCommand) Exec(context *opengl.Context, indexOffsetInBytes int) error {
	if c.target.framebuffer != nil {
		context.DeleteFramebuffer(c.target.framebuffer.native)
	}
	if c.target.texture != nil {
		context.DeleteTexture(c.target.texture.native)
	}
	return nil
}

type newImageFromImageCommand struct {
	result *Image
	img    *image.RGBA
	filter opengl.Filter
}

func (c *newImageFromImageCommand) Exec(context *opengl.Context, indexOffsetInBytes int) error {
	origSize := c.img.Bounds().Size()
	if origSize.X < 1 {
		return errors.New("graphics: width must be equal or more than 1.")
	}
	if origSize.Y < 1 {
		return errors.New("graphics: height must be equal or more than 1.")
	}
	w, h := c.img.Bounds().Size().X, c.img.Bounds().Size().Y
	if c.img.Bounds() != image.Rect(0, 0, NextPowerOf2Int(w), NextPowerOf2Int(h)) {
		panic(fmt.Sprintf("graphics: invalid image bounds: %v", c.img.Bounds()))
	}
	native, err := context.NewTexture(w, h, c.img.Pix, c.filter)
	if err != nil {
		return err
	}
	c.result.texture = &texture{
		native: native,
	}
	return nil
}

type newImageCommand struct {
	result *Image
	width  int
	height int
	filter opengl.Filter
}

func (c *newImageCommand) Exec(context *opengl.Context, indexOffsetInBytes int) error {
	w := NextPowerOf2Int(c.width)
	h := NextPowerOf2Int(c.height)
	if w < 1 {
		return errors.New("graphics: width must be equal or more than 1.")
	}
	if h < 1 {
		return errors.New("graphics: height must be equal or more than 1.")
	}
	native, err := context.NewTexture(w, h, nil, c.filter)
	if err != nil {
		return err
	}
	c.result.texture = &texture{
		native: native,
	}
	return nil
}

type newScreenFramebufferImageCommand struct {
	result *Image
	width  int
	height int
}

func (c *newScreenFramebufferImageCommand) Exec(context *opengl.Context, indexOffsetInBytes int) error {
	if c.width < 1 {
		return errors.New("graphics: width must be equal or more than 1.")
	}
	if c.height < 1 {
		return errors.New("graphics: height must be equal or more than 1.")
	}
	f := &framebuffer{
		native: context.ScreenFramebuffer(),
		flipY:  true,
	}
	c.result.framebuffer = f
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2014 Hajime Hoshi
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package graphics

import (
	"fmt"

	"github.com/hajimehoshi/ebiten/internal/affine"
	emath "github.com/hajimehoshi/ebiten/internal/math"
	"github.com/hajimehoshi/ebiten/internal/opengl"
	"github.com/hajimehoshi/ebiten/internal/web"
)

// arrayBufferLayoutPart is a part of an array buffer layout.
type arrayBufferLayoutPart struct {
	// TODO: This struct should belong to a program and know it.
	name     string
	dataType opengl.DataType
	num      int
}

// arrayBufferLayout is an array buffer layout.
//
// An array buffer in OpenGL is a buffer representing vertices and
// is passed to a vertex shader.
type arrayBufferLayout struct {
	parts []arrayBufferLayoutPart
	total int
}

// totalBytes returns the size in bytes for one element of the array buffer.
func (a *arrayBufferLayout) totalBytes() int {
	if a.total != 0 {
		return a.total
	}
	t := 0
	for _, p := range a.parts {
		t += p.dataType.SizeInBytes() * p.num
	}
	a.total = t
	return a.total
}

// newArrayBuffer creates OpenGL's buffer object for the array buffer.
func (a *arrayBufferLayout) newArrayBuffer() opengl.Buffer {
	return opengl.GetContext().NewArrayBuffer(a.totalBytes() * 4 * maxQuads)
}

// enable binds the array buffer the given program to use the array buffer.
func (a *arrayBufferLayout) enable(program opengl.Program) {
	for _, p := range a.parts {
		opengl.GetContext().EnableVertexAttribArray(program, p.name)
	}
	total := a.totalBytes()
	offset := 0
	for _, p := range a.parts {
		opengl.GetContext().VertexAttribPointer(program, p.name, p.num, p.dataType, total, offset)
		offset += p.dataType.SizeInBytes() * p.num
	}
}

// disable stops using the array buffer.
func (a *arrayBufferLayout) disable(program opengl.Program) {
	// TODO: Disabling should be done in reversed order?
	for _, p := range a.parts {
		opengl.GetContext().DisableVertexAttribArray(program, p.name)
	}
}

var (
	// theArrayBufferLayout is the array buffer layout for Ebiten.
	theArrayBufferLayout = arrayBufferLayout{
		// Note that GL_MAX_VERTEX_ATTRIBS is at least 16.
		parts: []arrayBufferLayoutPart{
			{
				name:     "vertex",
				dataType: opengl.Float,
				num:      2,
			},
			{
				name:     "tex_coord",
				dataType: opengl.Float,
				num:      4,
			},
		},
	}
)

// openGLState is a state for OpenGL.
type openGLState struct {
	// arrayBuffer is OpenGL's array buffer (vertices data).
	arrayBuffer opengl.Buffer

	// elementArrayBuffer is OpenGL's element array buffer (indices data).
	elementArrayBuffer opengl.Buffer

	// programNearest is OpenGL's program for rendering a texture with nearest filter.
	programNearest opengl.Program

	// programLinear is OpenGL's program for rendering a texture with linear filter.
	programLinear opengl.Program

	programScreen opengl.Program

	lastProgram                opengl.Program
	lastProjectionMatrix       []float32
	lastColorMatrix            []float32
	lastColorMatrixTranslation []float32
	lastSourceWidth            int
	lastSourceHeight           int
}

var (
	// theOpenGLState is the OpenGL state in the current process.
	theOpenGLState openGLState

	zeroBuffer  opengl.Buffer
	zeroProgram opengl.Program
)

const (
	indicesNum = 1 << 16
	maxQuads   = indicesNum / 6
)

// ResetGLState resets or initializes the current OpenGL state.
func ResetGLState() error {
	return theOpenGLState.reset()
}

// reset resets or initializes the OpenGL state.
func (s *openGLState) reset() error {
	if err := opengl.GetContext().Reset(); err != nil {
		return err
	}

	s.lastProgram = zeroProgram
	s.lastProjectionMatrix = nil
	s.lastColorMatrix = nil
	s.lastColorMatrixTranslation = nil
	s.lastSourceWidth = 0
	s.lastSourceHeight = 0

	// When context lost happens, deleting programs or buffers is not necessary.
	// However, it is not assumed that reset is called only when context lost happens.
	// Let's delete them explicitly.
	if s.programNearest != zeroProgram {
		opengl.GetContext().DeleteProgram(s.programNearest)
	}
	if s.programLinear != zeroProgram {
		opengl.GetContext().DeleteProgram(s.programLinear)
	}
	if s.programScreen != zeroProgram {
		opengl.GetContext().DeleteProgram(s.programScreen)
	}

	// On browsers (at least Chrome), buffers are already detached from the context
	// and must not be deleted by DeleteBuffer.
	if !web.IsBrowser() {
		if s.arrayBuffer != zeroBuffer {
			opengl.GetContext().DeleteBuffer(s.arrayBuffer)
		}
		if s.elementArrayBuffer != zeroBuffer {
			opengl.GetContext().DeleteBuffer(s.elementArrayBuffer)
		}
	}

	shaderVertexModelviewNative, err := opengl.GetContext().NewShader(opengl.VertexShader, shader(shaderVertexModelview))
	if err != nil {
		panic(fmt.Sprintf("graphics: shader compiling error:\n%s", err))
	}
	defer opengl.GetContext().DeleteShader(shaderVertexModelviewNative)

	shaderFragmentNearestNative, err := opengl.GetContext().NewShader(opengl.FragmentShader, shader(shaderFragmentNearest))
	if err != nil {
		panic(fmt.Sprintf("graphics: shader compiling error:\n%s", err))
	}
	defer opengl.GetContext().DeleteShader(shaderFragmentNearestNative)

	shaderFragmentLinearNative, err := opengl.GetContext().NewShader(opengl.FragmentShader, shader(shaderFragmentLinear))
	if err != nil {
		panic(fmt.Sprintf("graphics: shader compiling error:\n%s", err))
	}
	defer opengl.GetContext().DeleteShader(shaderFragmentLinearNative)

	shaderFragmentScreenNative, err := opengl.GetContext().NewShader(opengl.FragmentShader, shader(shaderFragmentScreen))
	if err != nil {
		panic(fmt.Sprintf("graphics: shader compiling error:\n%s", err))
	}
	defer opengl.GetContext().DeleteShader(shaderFragmentScreenNative)

	s.programNearest, err = opengl.GetContext().NewProgram([]opengl.Shader{
		shaderVertexModelviewNative,
		shaderFragmentNearestNative,
	})
	if err != nil {
		return err
	}

	s.programLinear, err = opengl.GetContext().NewProgram([]opengl.Shader{
		shaderVertexModelviewNative,
		shaderFragmentLinearNative,
	})
	if err != nil {
		return err
	}

	s.programScreen, err = opengl.GetContext().NewProgram([]opengl.Shader{
		shaderVertexModelviewNative,
		shaderFragmentScreenNative,
	})
	if err != nil {
		return err
	}

	s.arrayBuffer = theArrayBufferLayout.newArrayBuffer()

	// Note that the indices passed to NewElementArrayBuffer is not under GC management
	// in opengl package due to unsafe-way.
	// See NewElementArrayBuffer in context_mobile.go.
	s.elementArrayBuffer = opengl.GetContext().NewElementArrayBuffer(indicesNum * 2)

	return nil
}

// areSameFloat32Array returns a boolean indicating if a and b are deeply equal.
func areSameFloat32Array(a, b []float32) bool {
	if len(a) != len(b) {
		return false
	}
	for i := 0; i < len(a); i++ {
		if a[i] != b[i] {
			return false
		}
	}
	return true
}

// useProgram uses the program (programTexture).
func (s *openGLState) useProgram(proj []float32, texture opengl.Texture, dst, src *Image, colorM *affine.ColorM, filter Filter) {
	c := opengl.GetContext()

	var program opengl.Program
	switch filter {
	case FilterNearest:
		program = s.programNearest
	case FilterLinear:
		program = s.programLinear
	case FilterScreen:
		program = s.programScreen
	default:
		panic("not reached")
	}

	if s.lastProgram != program {
		c.UseProgram(program)
		if s.lastProgram != zeroProgram {
			theArrayBufferLayout.disable(s.lastProgram)
		}
		theArrayBufferLayout.enable(program)

		s.lastProgram = program
		s.lastProjectionMatrix = nil
		s.lastColorMatrix = nil
		s.lastColorMatrixTranslation = nil
		s.lastSourceWidth = 0
		s.lastSourceHeight = 0
		c.BindBuffer(opengl.ArrayBuffer, s.arrayBuffer)
		c.BindBuffer(opengl.ElementArrayBuffer, s.elementArrayBuffer)
		c.UniformInt(program, "texture", 0)
	}

	if !areSameFloat32Array(s.lastProjectionMatrix, proj) {
		c.UniformFloats(program, "projection_matrix", proj)
		if s.lastProjectionMatrix == nil {
			s.lastProjectionMatrix = make([]float32, 16)
		}
		// (*framebuffer).projectionMatrix is always same for the same framebuffer.
		// It's OK to hold the reference without copying.
		s.lastProjectionMatrix = proj
	}

	esBody, esTranslate := colorM.UnsafeElements()

	if !areSameFloat32Array(s.lastColorMatrix, esBody) {
		c.UniformFloats(program, "color_matrix", esBody)
		if s.lastColorMatrix == nil {
			s.lastColorMatrix = make([]float32, 16)
		}
		// ColorM's elements are immutable. It's OK to hold the reference without copying.
		s.lastColorMatrix = esBody
	}
	if !areSameFloat32Array(s.lastColorMatrixTranslation, esTranslate) {
		c.UniformFloats(program, "color_matrix_translation", esTranslate)
		if s.lastColorMatrixTranslation == nil {
			s.lastColorMatrixTranslation = make([]float32, 4)
		}
		// ColorM's elements are immutable. It's OK to hold the reference without copying.
		s.lastColorMatrixTranslation = esTranslate
	}

	sw, sh := src.Size()
	sw = emath.NextPowerOf2Int(sw)
	sh = emath.NextPowerOf2Int(sh)

	if s.lastSourceWidth != sw || s.lastSourceHeight != sh {
		c.UniformFloats(program, "source_size", []float32{float32(sw), float32(sh)})
		s.lastSourceWidth = sw
		s.lastSourceHeight = sh
	}

	if program == s.programScreen {
		sw, _ := src.Size()
		dw, _ := dst.Size()
		scale := float32(dw) / float32(sw)
		c.UniformFloat(program, "scale", scale)
	}

	// We don't have to call gl.ActiveTexture here: GL_TEXTURE0 is the default active texture
	// See also: https://www.opengl.org/sdk/docs/man2/xhtml/glActiveTexture.xml
	c.BindTexture(texture)
}
<commit_msg>graphics: Refactoring<commit_after>// Copyright 2014 Hajime Hoshi
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package graphics

import (
	"fmt"

	"github.com/hajimehoshi/ebiten/internal/affine"
	emath "github.com/hajimehoshi/ebiten/internal/math"
	"github.com/hajimehoshi/ebiten/internal/opengl"
	"github.com/hajimehoshi/ebiten/internal/web"
)

// arrayBufferLayoutPart is a part of an array buffer layout.
type arrayBufferLayoutPart struct {
	// TODO: This struct should belong to a program and know it.
	name     string
	dataType opengl.DataType
	num      int
}

// arrayBufferLayout is an array buffer layout.
//
// An array buffer in OpenGL is a buffer representing vertices and
// is passed to a vertex shader.
type arrayBufferLayout struct {
	parts []arrayBufferLayoutPart
	total int
}

// totalBytes returns the size in bytes for one element of the array buffer.
func (a *arrayBufferLayout) totalBytes() int {
	if a.total != 0 {
		return a.total
	}
	t := 0
	for _, p := range a.parts {
		t += p.dataType.SizeInBytes() * p.num
	}
	a.total = t
	return a.total
}

// newArrayBuffer creates OpenGL's buffer object for the array buffer.
func (a *arrayBufferLayout) newArrayBuffer() opengl.Buffer {
	return opengl.GetContext().NewArrayBuffer(a.totalBytes() * indicesNum)
}

// enable binds the array buffer the given program to use the array buffer.
func (a *arrayBufferLayout) enable(program opengl.Program) {
	for _, p := range a.parts {
		opengl.GetContext().EnableVertexAttribArray(program, p.name)
	}
	total := a.totalBytes()
	offset := 0
	for _, p := range a.parts {
		opengl.GetContext().VertexAttribPointer(program, p.name, p.num, p.dataType, total, offset)
		offset += p.dataType.SizeInBytes() * p.num
	}
}

// disable stops using the array buffer.
func (a *arrayBufferLayout) disable(program opengl.Program) {
	// TODO: Disabling should be done in reversed order?
	for _, p := range a.parts {
		opengl.GetContext().DisableVertexAttribArray(program, p.name)
	}
}

var (
	// theArrayBufferLayout is the array buffer layout for Ebiten.
	theArrayBufferLayout = arrayBufferLayout{
		// Note that GL_MAX_VERTEX_ATTRIBS is at least 16.
		parts: []arrayBufferLayoutPart{
			{
				name:     "vertex",
				dataType: opengl.Float,
				num:      2,
			},
			{
				name:     "tex_coord",
				dataType: opengl.Float,
				num:      4,
			},
		},
	}
)

// openGLState is a state for OpenGL.
type openGLState struct {
	// arrayBuffer is OpenGL's array buffer (vertices data).
	arrayBuffer opengl.Buffer

	// elementArrayBuffer is OpenGL's element array buffer (indices data).
	elementArrayBuffer opengl.Buffer

	// programNearest is OpenGL's program for rendering a texture with nearest filter.
	programNearest opengl.Program

	// programLinear is OpenGL's program for rendering a texture with linear filter.
	programLinear opengl.Program

	programScreen opengl.Program

	lastProgram                opengl.Program
	lastProjectionMatrix       []float32
	lastColorMatrix            []float32
	lastColorMatrixTranslation []float32
	lastSourceWidth            int
	lastSourceHeight           int
}

var (
	// theOpenGLState is the OpenGL state in the current process.
	theOpenGLState openGLState

	zeroBuffer  opengl.Buffer
	zeroProgram opengl.Program
)

const (
	indicesNum   = 1 << 16
	maxTriangles = indicesNum / 3
	maxQuads     = maxTriangles / 2
)

// ResetGLState resets or initializes the current OpenGL state.
func ResetGLState() error {
	return theOpenGLState.reset()
}

// reset resets or initializes the OpenGL state.
func (s *openGLState) reset() error {
	if err := opengl.GetContext().Reset(); err != nil {
		return err
	}

	s.lastProgram = zeroProgram
	s.lastProjectionMatrix = nil
	s.lastColorMatrix = nil
	s.lastColorMatrixTranslation = nil
	s.lastSourceWidth = 0
	s.lastSourceHeight = 0

	// When context lost happens, deleting programs or buffers is not necessary.
	// However, it is not assumed that reset is called only when context lost happens.
	// Let's delete them explicitly.
	if s.programNearest != zeroProgram {
		opengl.GetContext().DeleteProgram(s.programNearest)
	}
	if s.programLinear != zeroProgram {
		opengl.GetContext().DeleteProgram(s.programLinear)
	}
	if s.programScreen != zeroProgram {
		opengl.GetContext().DeleteProgram(s.programScreen)
	}

	// On browsers (at least Chrome), buffers are already detached from the context
	// and must not be deleted by DeleteBuffer.
	if !web.IsBrowser() {
		if s.arrayBuffer != zeroBuffer {
			opengl.GetContext().DeleteBuffer(s.arrayBuffer)
		}
		if s.elementArrayBuffer != zeroBuffer {
			opengl.GetContext().DeleteBuffer(s.elementArrayBuffer)
		}
	}

	shaderVertexModelviewNative, err := opengl.GetContext().NewShader(opengl.VertexShader, shader(shaderVertexModelview))
	if err != nil {
		panic(fmt.Sprintf("graphics: shader compiling error:\n%s", err))
	}
	defer opengl.GetContext().DeleteShader(shaderVertexModelviewNative)

	shaderFragmentNearestNative, err := opengl.GetContext().NewShader(opengl.FragmentShader, shader(shaderFragmentNearest))
	if err != nil {
		panic(fmt.Sprintf("graphics: shader compiling error:\n%s", err))
	}
	defer opengl.GetContext().DeleteShader(shaderFragmentNearestNative)

	shaderFragmentLinearNative, err := opengl.GetContext().NewShader(opengl.FragmentShader, shader(shaderFragmentLinear))
	if err != nil {
		panic(fmt.Sprintf("graphics: shader compiling error:\n%s", err))
	}
	defer opengl.GetContext().DeleteShader(shaderFragmentLinearNative)

	shaderFragmentScreenNative, err := opengl.GetContext().NewShader(opengl.FragmentShader, shader(shaderFragmentScreen))
	if err != nil {
		panic(fmt.Sprintf("graphics: shader compiling error:\n%s", err))
	}
	defer opengl.GetContext().DeleteShader(shaderFragmentScreenNative)

	s.programNearest, err = opengl.GetContext().NewProgram([]opengl.Shader{
		shaderVertexModelviewNative,
		shaderFragmentNearestNative,
	})
	if err != nil {
		return err
	}

	s.programLinear, err = opengl.GetContext().NewProgram([]opengl.Shader{
		shaderVertexModelviewNative,
		shaderFragmentLinearNative,
	})
	if err != nil {
		return err
	}

	s.programScreen, err = opengl.GetContext().NewProgram([]opengl.Shader{
		shaderVertexModelviewNative,
		shaderFragmentScreenNative,
	})
	if err != nil {
		return err
	}

	s.arrayBuffer = theArrayBufferLayout.newArrayBuffer()

	// Note that the indices passed to NewElementArrayBuffer is not under GC management
	// in opengl package due to unsafe-way.
	// See NewElementArrayBuffer in context_mobile.go.
	s.elementArrayBuffer = opengl.GetContext().NewElementArrayBuffer(indicesNum * 2)

	return nil
}

// areSameFloat32Array returns a boolean indicating if a and b are deeply equal.
func areSameFloat32Array(a, b []float32) bool {
	if len(a) != len(b) {
		return false
	}
	for i := 0; i < len(a); i++ {
		if a[i] != b[i] {
			return false
		}
	}
	return true
}

// useProgram uses the program (programTexture).
func (s *openGLState) useProgram(proj []float32, texture opengl.Texture, dst, src *Image, colorM *affine.ColorM, filter Filter) {
	c := opengl.GetContext()

	var program opengl.Program
	switch filter {
	case FilterNearest:
		program = s.programNearest
	case FilterLinear:
		program = s.programLinear
	case FilterScreen:
		program = s.programScreen
	default:
		panic("not reached")
	}

	if s.lastProgram != program {
		c.UseProgram(program)
		if s.lastProgram != zeroProgram {
			theArrayBufferLayout.disable(s.lastProgram)
		}
		theArrayBufferLayout.enable(program)

		s.lastProgram = program
		s.lastProjectionMatrix = nil
		s.lastColorMatrix = nil
		s.lastColorMatrixTranslation = nil
		s.lastSourceWidth = 0
		s.lastSourceHeight = 0
		c.BindBuffer(opengl.ArrayBuffer, s.arrayBuffer)
		c.BindBuffer(opengl.ElementArrayBuffer, s.elementArrayBuffer)
		c.UniformInt(program, "texture", 0)
	}

	if !areSameFloat32Array(s.lastProjectionMatrix, proj) {
		c.UniformFloats(program, "projection_matrix", proj)
		if s.lastProjectionMatrix == nil {
			s.lastProjectionMatrix = make([]float32, 16)
		}
		// (*framebuffer).projectionMatrix is always same for the same framebuffer.
		// It's OK to hold the reference without copying.
		s.lastProjectionMatrix = proj
	}

	esBody, esTranslate := colorM.UnsafeElements()

	if !areSameFloat32Array(s.lastColorMatrix, esBody) {
		c.UniformFloats(program, "color_matrix", esBody)
		if s.lastColorMatrix == nil {
			s.lastColorMatrix = make([]float32, 16)
		}
		// ColorM's elements are immutable. It's OK to hold the reference without copying.
		s.lastColorMatrix = esBody
	}
	if !areSameFloat32Array(s.lastColorMatrixTranslation, esTranslate) {
		c.UniformFloats(program, "color_matrix_translation", esTranslate)
		if s.lastColorMatrixTranslation == nil {
			s.lastColorMatrixTranslation = make([]float32, 4)
		}
		// ColorM's elements are immutable. It's OK to hold the reference without copying.
		s.lastColorMatrixTranslation = esTranslate
	}

	sw, sh := src.Size()
	sw = emath.NextPowerOf2Int(sw)
	sh = emath.NextPowerOf2Int(sh)

	if s.lastSourceWidth != sw || s.lastSourceHeight != sh {
		c.UniformFloats(program, "source_size", []float32{float32(sw), float32(sh)})
		s.lastSourceWidth = sw
		s.lastSourceHeight = sh
	}

	if program == s.programScreen {
		sw, _ := src.Size()
		dw, _ := dst.Size()
		scale := float32(dw) / float32(sw)
		c.UniformFloat(program, "scale", scale)
	}

	// We don't have to call gl.ActiveTexture here: GL_TEXTURE0 is the default active texture
	// See also: https://www.opengl.org/sdk/docs/man2/xhtml/glActiveTexture.xml
	c.BindTexture(texture)
}
<|endoftext|>


<commit_before>package telegram

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"os"

	tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
	"github.com/scbizu/Astral/internal/plugin/hub"
	"github.com/scbizu/Astral/pkg/getcert"
	"github.com/scbizu/Astral/pkg/talker"
	"github.com/scbizu/Astral/pkg/talker/dce"
	"github.com/sirupsen/logrus"
)

type Bot struct {
	bot         *tgbotapi.BotAPI
	isDebugMode bool
}

func NewBot(isDebugMode bool) (*Bot, error) {
	bot := new(Bot)
	tgConn, err := ConnectTG()
	if err != nil {
		return nil, err
	}
	bot.bot = tgConn
	if isDebugMode {
		logrus.SetLevel(logrus.DebugLevel)
		bot.isDebugMode = true
		tgConn.Debug = true
		logrus.Infof("bot auth passed as %s", tgConn.Self.UserName)
	}
	return bot, nil
}

func listenWebhook() {
	port := fmt.Sprintf(":%s", os.Getenv("LISTENPORT"))
	http.ListenAndServe(port, nil)
}

func (b *Bot) ServeBotUpdateMessage() error {
	go listenWebhook()
	b.bot.RemoveWebhook()
	cert := getcert.NewDomainCert(tgAPIDomain)
	domainWithToken := fmt.Sprintf("%s/%s", cert.GetDomain(), token)
	if _, err := b.bot.SetWebhook(tgbotapi.NewWebhook(domainWithToken)); err != nil {
		logrus.Errorf("notify webhook failed:%s", err.Error())
		return err
	}
	if b.isDebugMode {
		logrus.SetLevel(logrus.DebugLevel)
		info, err := b.bot.GetWebhookInfo()
		if err != nil {
			return err
		}
		logrus.Debug(info.LastErrorMessage, info.LastErrorDate)
	}
	pattern := fmt.Sprintf("/tg/%s", token)
	updatesMsgChannel := b.bot.ListenForWebhook(pattern)

	logrus.Debugf("msg in channel:%d", len(updatesMsgChannel))
	for update := range updatesMsgChannel {
		logrus.Debugf("[raw msg]:%#v\n", update)

		if update.Message == nil {
			continue
		}
		pluginHub := hub.NewTGPluginHub(update.Message)
		msg := pluginHub.RegistTGEnabledPlugins(update.Message)
		if isMsgBadRequest(msg) {
			msg = tgbotapi.NewMessage(update.Message.Chat.ID, "Astral服务酱表示不想理你")
		}

		mbNames, ok := isMsgNewMember(update)
		if ok {
			msg = tgbotapi.NewMessage(update.Message.Chat.ID, fmt.Sprintf("撒花欢迎新基佬(%v)入群", mbNames))
		}

		_, ok = isMsgLeftMember(update)
		if ok {
			// drop the member left message
			continue
		}

		msg.ReplyToMessageID = update.Message.MessageID
		b.bot.Send(msg)
	}
	return nil
}

func (b *Bot) ServePushAstralServerMessage() {
	go healthCheck(b.bot)
	registerDCEServer(b.bot)
}

func isMsgNewMember(update tgbotapi.Update) ([]string, bool) {
	members := update.Message.NewChatMembers
	if members == nil {
		return nil, false
	}
	if len(*members) == 0 {
		return nil, false
	}
	var mbNames []string
	for _, m := range *members {
		mbNames = append(mbNames, m.String())
	}
	return mbNames, true
}

func isMsgLeftMember(update tgbotapi.Update) (string, bool) {
	if update.Message.LeftChatMember == nil {
		return "", false
	}
	mbName := update.Message.LeftChatMember.String()
	return mbName, true
}

func isMsgBadRequest(msg tgbotapi.MessageConfig) bool {
	if msg.Text == "" || msg.ChatID == 0 {
		return true
	}
	return false
}

func registerDCEServer(bot *tgbotapi.BotAPI) {
	dceListenHandler := func(w http.ResponseWriter, r *http.Request) {
		if r.Method == http.MethodPost {
			r.ParseForm()
			body, err := ioutil.ReadAll(r.Body)
			if err != nil {
				logrus.Errorf("dce: read webhook msg failed: %q", err.Error())
				w.WriteHeader(http.StatusInternalServerError)
				w.Write([]byte(err.Error()))
				return
			}
			defer r.Body.Close()
			dceObj, err := dce.NewDCEObj(string(body))
			if err != nil {
				logrus.Errorf("dce: read webhook msg failed: %q", err.Error())
				w.WriteHeader(http.StatusInternalServerError)
				w.Write([]byte(err.Error()))
				return
			}
			// TODO: talker need to be REMOVED ASAP
			noti := talker.NewNotifaction(dceObj.GetRepoName(),
				dceObj.GetStageMap(), dceObj.GetCommitMsg(),
				dceObj.GetBuildDuration())

			respMsg, err := bot.Send(noti.Notify())
			if err != nil {
				logrus.Errorf("telegram bot: send server info failed: %q", err.Error())
				return
			}

			w.WriteHeader(http.StatusOK)
			w.Write([]byte(respMsg.Text))
		} else {
			w.WriteHeader(http.StatusForbidden)
			w.Write([]byte("Astral has denied your request"))
		}
	}

	http.HandleFunc("/alert/dce", dceListenHandler)

	port := fmt.Sprintf(":%s", os.Getenv("LISTENPORT"))

	http.ListenAndServe(port, nil)
}
<commit_msg>internal/telegram: debug webhook<commit_after>package telegram

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"os"

	tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
	"github.com/scbizu/Astral/internal/plugin/hub"
	"github.com/scbizu/Astral/pkg/getcert"
	"github.com/scbizu/Astral/pkg/talker"
	"github.com/scbizu/Astral/pkg/talker/dce"
	"github.com/sirupsen/logrus"
)

type Bot struct {
	bot         *tgbotapi.BotAPI
	isDebugMode bool
}

func NewBot(isDebugMode bool) (*Bot, error) {
	bot := new(Bot)
	tgConn, err := ConnectTG()
	if err != nil {
		return nil, err
	}
	bot.bot = tgConn
	if isDebugMode {
		logrus.SetLevel(logrus.DebugLevel)
		bot.isDebugMode = true
		tgConn.Debug = true
		logrus.Infof("bot auth passed as %s", tgConn.Self.UserName)
	}
	return bot, nil
}

func listenWebhook() {
	port := fmt.Sprintf(":%s", os.Getenv("LISTENPORT"))
	http.ListenAndServe(port, nil)
}

func (b *Bot) ServeBotUpdateMessage() error {
	go listenWebhook()
	b.bot.RemoveWebhook()
	cert := getcert.NewDomainCert(tgAPIDomain)
	domainWithToken := fmt.Sprintf("%s/%s", cert.GetDomain(), token)
	if _, err := b.bot.SetWebhook(tgbotapi.NewWebhook(domainWithToken)); err != nil {
		logrus.Errorf("notify webhook failed:%s", err.Error())
		return err
	}
	if b.isDebugMode {
		logrus.SetLevel(logrus.DebugLevel)
		info, err := b.bot.GetWebhookInfo()
		if err != nil {
			return err
		}
		logrus.Debug(info.LastErrorMessage, info.LastErrorDate)
	}
	pattern := fmt.Sprintf("/tg/%s", token)
	updatesMsgChannel := b.bot.ListenForWebhook(pattern)

	logrus.Debugf("msg in channel:%d", len(updatesMsgChannel))
	for update := range updatesMsgChannel {
		logrus.Debugf("[raw msg]:%#v\n", update)

		if update.Message == nil {
			continue
		}
		pluginHub := hub.NewTGPluginHub(update.Message)
		msg := pluginHub.RegistTGEnabledPlugins(update.Message)
		if isMsgBadRequest(msg) {
			msg = tgbotapi.NewMessage(update.Message.Chat.ID, "Astral服务酱表示不想理你")
		}

		mbNames, ok := isMsgNewMember(update)
		if ok {
			msg = tgbotapi.NewMessage(update.Message.Chat.ID, fmt.Sprintf("撒花欢迎新基佬(%v)入群", mbNames))
		}

		_, ok = isMsgLeftMember(update)
		if ok {
			// drop the member left message
			continue
		}

		msg.ReplyToMessageID = update.Message.MessageID
		b.bot.Send(msg)
	}
	return nil
}

func (b *Bot) ServePushAstralServerMessage() {
	go healthCheck(b.bot)
	registerDCEServer(b.bot)
}

func isMsgNewMember(update tgbotapi.Update) ([]string, bool) {
	members := update.Message.NewChatMembers
	if members == nil {
		return nil, false
	}
	if len(*members) == 0 {
		return nil, false
	}
	var mbNames []string
	for _, m := range *members {
		mbNames = append(mbNames, m.String())
	}
	return mbNames, true
}

func isMsgLeftMember(update tgbotapi.Update) (string, bool) {
	if update.Message.LeftChatMember == nil {
		return "", false
	}
	mbName := update.Message.LeftChatMember.String()
	return mbName, true
}

func isMsgBadRequest(msg tgbotapi.MessageConfig) bool {
	if msg.Text == "" || msg.ChatID == 0 {
		return true
	}
	return false
}

func registerDCEServer(bot *tgbotapi.BotAPI) {
	dceListenHandler := func(w http.ResponseWriter, r *http.Request) {
		if r.Method == http.MethodPost {
			r.ParseForm()
			body, err := ioutil.ReadAll(r.Body)
			if err != nil {
				logrus.Errorf("dce: read webhook msg failed: %q", err.Error())
				w.WriteHeader(http.StatusInternalServerError)
				w.Write([]byte(err.Error()))
				return
			}
			defer r.Body.Close()

			logrus.Infof("dce webhook: %s", string(body))

			dceObj, err := dce.NewDCEObj(string(body))
			if err != nil {
				logrus.Errorf("dce: read webhook msg failed: %q", err.Error())
				w.WriteHeader(http.StatusInternalServerError)
				w.Write([]byte(err.Error()))
				return
			}
			// TODO: talker need to be REMOVED ASAP
			noti := talker.NewNotifaction(dceObj.GetRepoName(),
				dceObj.GetStageMap(), dceObj.GetCommitMsg(),
				dceObj.GetBuildDuration())

			respMsg, err := bot.Send(noti.Notify())
			if err != nil {
				logrus.Errorf("telegram bot: send server info failed: %q", err.Error())
				return
			}

			w.WriteHeader(http.StatusOK)
			w.Write([]byte(respMsg.Text))
		} else {
			w.WriteHeader(http.StatusForbidden)
			w.Write([]byte("Astral has denied your request"))
		}
	}

	http.HandleFunc("/alert/dce", dceListenHandler)

	port := fmt.Sprintf(":%s", os.Getenv("LISTENPORT"))

	http.ListenAndServe(port, nil)
}
<|endoftext|>


<commit_before>// Package adhier provides an algorithm for adaptive hierarchical interpolation
// with local refinements.
package adhier

import (
	"runtime"
	"sync"
)

// Grid is a sparse grid in [0, 1]^n.
type Grid interface {
	Compute(indices []uint64) []float64
	Refine(indices []uint64) []uint64
}

// Basis is a functional basis in [0, 1]^n.
type Basis interface {
	Compute(index []uint64, point []float64) float64
	Integrate(index []uint64) float64
}

// Interpolator represents a particular instantiation of the algorithm.
type Interpolator struct {
	grid   Grid
	basis  Basis
	config Config
}

// New creates an instance of the algorithm for the given configuration.
func New(grid Grid, basis Basis, config *Config) *Interpolator {
	interpolator := &Interpolator{
		grid:   grid,
		basis:  basis,
		config: *config,
	}

	config = &interpolator.config
	if config.Workers == 0 {
		config.Workers = uint(runtime.GOMAXPROCS(0))
	}

	return interpolator
}

// Compute constructs an interpolant for a quantity of interest.
func (self *Interpolator) Compute(target Target) *Surrogate {
	config := &self.config

	ni, no := target.Dimensions()

	surrogate := newSurrogate(ni, no)

	hash := newHash(ni)
	queue := newQueue(ni, config.MinLevel, config.MaxLevel, config.Rate)

	// The zeroth level is assumed to have only one node, and the order of that
	// node is assumed to be zero.
	na, np := uint(1), uint(0)

	indices := make([]uint64, na*ni)

	for k := uint(0); ; k++ {
		target.Monitor(k, np, na)

		surrogate.resize(np + na)
		copy(surrogate.Indices[np*ni:], indices)

		nodes := self.grid.Compute(indices)

		values := invoke(target.Compute, nodes, ni, no, config.Workers)
		approximations := approximate(self.basis, surrogate.Indices[:np*ni],
			surrogate.Surpluses[:np*no], nodes, ni, no, config.Workers)

		surpluses := surrogate.Surpluses[np*no : (np+na)*no]
		for i := uint(0); i < na*no; i++ {
			surpluses[i] = values[i] - approximations[i]
		}

		if np+na >= config.MaxNodes {
			break
		}

		scores := measure(self.basis, indices, ni)
		for i := uint(0); i < na; i++ {
			scores[i] = target.Refine(nodes[i*ni:(i+1)*ni], surpluses[i*no:(i+1)*no], scores[i])
		}

		queue.push(indices, scores)
		indices = queue.pull()
		if len(indices) == 0 {
			break
		}

		indices = hash.unique(self.grid.Refine(indices))

		np += na
		na = uint(len(indices)) / ni

		// Trim if there are excessive nodes.
		if np+na > config.MaxNodes {
			na = config.MaxNodes - np
			indices = indices[:na*ni]
		}
	}

	surrogate.finalize(np + na)
	return surrogate
}

// Evaluate computes the values of a surrogate at a set of points.
func (self *Interpolator) Evaluate(surrogate *Surrogate, points []float64) []float64 {
	return approximate(self.basis, surrogate.Indices, surrogate.Surpluses, points,
		surrogate.Inputs, surrogate.Outputs, self.config.Workers)
}

// Integrate computes the integral of a surrogate over [0, 1]^n.
func (self *Interpolator) Integrate(surrogate *Surrogate) []float64 {
	return integrate(self.basis, surrogate.Indices, surrogate.Surpluses,
		surrogate.Inputs, surrogate.Outputs)
}

func approximate(basis Basis, indices []uint64, surpluses, points []float64,
	ni, no, nw uint) []float64 {

	nn, np := uint(len(indices))/ni, uint(len(points))/ni

	values := make([]float64, np*no)

	jobs := make(chan uint, np)
	group := sync.WaitGroup{}
	group.Add(int(np))

	for i := uint(0); i < nw; i++ {
		go func() {
			for j := range jobs {
				point := points[j*ni : (j+1)*ni]
				value := values[j*no : (j+1)*no]

				for k := uint(0); k < nn; k++ {
					weight := basis.Compute(indices[k*ni:(k+1)*ni], point)
					if weight == 0 {
						continue
					}
					for l := uint(0); l < no; l++ {
						value[l] += weight * surpluses[k*no+l]
					}
				}

				group.Done()
			}
		}()
	}

	for i := uint(0); i < np; i++ {
		jobs <- i
	}

	group.Wait()
	close(jobs)

	return values
}

func invoke(compute func([]float64, []float64), nodes []float64, ni, no, nw uint) []float64 {
	nn := uint(len(nodes)) / ni

	values := make([]float64, nn*no)

	jobs := make(chan uint, nn)
	group := sync.WaitGroup{}
	group.Add(int(nn))

	for i := uint(0); i < nw; i++ {
		go func() {
			for j := range jobs {
				compute(nodes[j*ni:(j+1)*ni], values[j*no:(j+1)*no])
				group.Done()
			}
		}()
	}

	for i := uint(0); i < nn; i++ {
		jobs <- i
	}

	group.Wait()
	close(jobs)

	return values
}

func integrate(basis Basis, indices []uint64, surpluses []float64, ni, no uint) []float64 {
	nn := uint(len(indices)) / ni

	value := make([]float64, no)

	for i := uint(0); i < nn; i++ {
		weight := basis.Integrate(indices[i*ni : (i+1)*ni])
		for j := uint(0); j < no; j++ {
			value[j] += weight * surpluses[i*no+j]
		}
	}

	return value
}

func measure(basis Basis, indices []uint64, ni uint) []float64 {
	nn := uint(len(indices)) / ni

	volumes := make([]float64, nn)

	for i := uint(0); i < nn; i++ {
		volumes[i] = basis.Integrate(indices[i*ni : (i+1)*ni])
	}

	return volumes
}
<commit_msg>adhier: a cosmetic change<commit_after>// Package adhier provides an algorithm for adaptive hierarchical interpolation
// with local refinements.
package adhier

import (
	"runtime"
	"sync"
)

// Grid is a sparse grid in [0, 1]^n.
type Grid interface {
	Compute(indices []uint64) []float64
	Refine(indices []uint64) []uint64
}

// Basis is a functional basis in [0, 1]^n.
type Basis interface {
	Compute(index []uint64, point []float64) float64
	Integrate(index []uint64) float64
}

// Interpolator represents a particular instantiation of the algorithm.
type Interpolator struct {
	grid   Grid
	basis  Basis
	config Config
}

// New creates an instance of the algorithm for the given configuration.
func New(grid Grid, basis Basis, config *Config) *Interpolator {
	interpolator := &Interpolator{
		grid:   grid,
		basis:  basis,
		config: *config,
	}

	config = &interpolator.config
	if config.Workers == 0 {
		config.Workers = uint(runtime.GOMAXPROCS(0))
	}

	return interpolator
}

// Compute constructs an interpolant for a quantity of interest.
func (self *Interpolator) Compute(target Target) *Surrogate {
	config := &self.config

	ni, no := target.Dimensions()

	surrogate := newSurrogate(ni, no)

	hash := newHash(ni)
	queue := newQueue(ni, config.MinLevel, config.MaxLevel, config.Rate)

	// The zeroth level is assumed to have only one node, and the order of that
	// node is assumed to be zero.
	na, np := uint(1), uint(0)

	indices := make([]uint64, na*ni)

	for k := uint(0); ; k++ {
		target.Monitor(k, np, na)

		surrogate.resize(np + na)
		copy(surrogate.Indices[np*ni:], indices)

		nodes := self.grid.Compute(indices)

		values := invoke(target.Compute, nodes, ni, no, config.Workers)
		approximations := approximate(self.basis, surrogate.Indices[:np*ni],
			surrogate.Surpluses[:np*no], nodes, ni, no, config.Workers)

		surpluses := surrogate.Surpluses[np*no : (np+na)*no]
		for i := uint(0); i < na*no; i++ {
			surpluses[i] = values[i] - approximations[i]
		}

		if np+na >= config.MaxNodes {
			break
		}

		scores := measure(self.basis, indices, ni)
		for i := uint(0); i < na; i++ {
			scores[i] = target.Refine(nodes[i*ni:(i+1)*ni], surpluses[i*no:(i+1)*no], scores[i])
		}

		queue.push(indices, scores)
		indices = queue.pull()
		if len(indices) == 0 {
			break
		}

		indices = hash.unique(self.grid.Refine(indices))

		np += na
		na = uint(len(indices)) / ni

		// Trim if there are excessive nodes.
		if np+na > config.MaxNodes {
			na = config.MaxNodes - np
			indices = indices[:na*ni]
		}
	}

	surrogate.finalize(np + na)
	return surrogate
}

// Evaluate computes the values of a surrogate at a set of points.
func (self *Interpolator) Evaluate(surrogate *Surrogate, points []float64) []float64 {
	return approximate(self.basis, surrogate.Indices, surrogate.Surpluses, points,
		surrogate.Inputs, surrogate.Outputs, self.config.Workers)
}

// Integrate computes the integral of a surrogate over [0, 1]^n.
func (self *Interpolator) Integrate(surrogate *Surrogate) []float64 {
	return integrate(self.basis, surrogate.Indices, surrogate.Surpluses,
		surrogate.Inputs, surrogate.Outputs)
}

func approximate(basis Basis, indices []uint64, surpluses, points []float64,
	ni, no, nw uint) []float64 {

	nn, np := uint(len(indices))/ni, uint(len(points))/ni

	values := make([]float64, np*no)

	jobs := make(chan uint, np)
	group := sync.WaitGroup{}
	group.Add(int(np))

	for i := uint(0); i < nw; i++ {
		go func() {
			for j := range jobs {
				point := points[j*ni : (j+1)*ni]
				value := values[j*no : (j+1)*no]

				for k := uint(0); k < nn; k++ {
					weight := basis.Compute(indices[k*ni:(k+1)*ni], point)
					if weight == 0 {
						continue
					}
					for l := uint(0); l < no; l++ {
						value[l] += weight * surpluses[k*no+l]
					}
				}

				group.Done()
			}
		}()
	}

	for i := uint(0); i < np; i++ {
		jobs <- i
	}

	group.Wait()
	close(jobs)

	return values
}

func invoke(compute func([]float64, []float64), nodes []float64, ni, no, nw uint) []float64 {
	nn := uint(len(nodes)) / ni

	values := make([]float64, nn*no)

	jobs := make(chan uint, nn)
	group := sync.WaitGroup{}
	group.Add(int(nn))

	for i := uint(0); i < nw; i++ {
		go func() {
			for j := range jobs {
				compute(nodes[j*ni:(j+1)*ni], values[j*no:(j+1)*no])
				group.Done()
			}
		}()
	}

	for i := uint(0); i < nn; i++ {
		jobs <- i
	}

	group.Wait()
	close(jobs)

	return values
}

func integrate(basis Basis, indices []uint64, surpluses []float64, ni, no uint) []float64 {
	nn := uint(len(indices)) / ni

	value := make([]float64, no)

	for i := uint(0); i < nn; i++ {
		volume := basis.Integrate(indices[i*ni : (i+1)*ni])
		for j := uint(0); j < no; j++ {
			value[j] += surpluses[i*no+j] * volume
		}
	}

	return value
}

func measure(basis Basis, indices []uint64, ni uint) []float64 {
	nn := uint(len(indices)) / ni

	volumes := make([]float64, nn)

	for i := uint(0); i < nn; i++ {
		volumes[i] = basis.Integrate(indices[i*ni : (i+1)*ni])
	}

	return volumes
}
<|endoftext|>


<commit_before>package simra

import (
	"context"

	"github.com/pankona/gomo-simra/simra/fps"
	"github.com/pankona/gomo-simra/simra/peer"
)

// Spriter represents an interface of Sprite
type Spriter interface {
	// ReplaceTexture replaces sprite's texture with specified image resource.
	ReplaceTexture(texture *Texture)
	// AddTouchListener registers a listener for touch event.
	// Touch event will be notified when "sprite" is touched.
	AddTouchListener(listener peer.TouchListener)
	// RemoveAllTouchListener removes all listeners already registered.
	RemoveAllTouchListener()
	// AddAnimationSet adds a specified AnimationSet to sprite
	AddAnimationSet(animationName string, set *AnimationSet)
	// StartAnimation starts animation by specified animation name
	StartAnimation(animationName string, shouldLoop bool, animationEndCallback func())
	// StopAnimation stops animation
	StopAnimation()
	// ProgressAnimation progresses registered animation
	ProgressAnimation()
	// SetPosition sets sprite's position
	SetPosition(x, y int)
	// SetPositionX sets sprite's position X
	SetPositionX(x int)
	// SetPositionY sets sprite's position Y
	SetPositionY(y int)
	// SetScale sets sprite's size
	SetScale(w, h int)
	// SetScaleW sets sprite's size W
	SetScaleW(w int)
	// SetScaleH sets sprite's size H
	SetScaleH(h int)
	// GetPosition gets sprites position
	GetPosition() Position
	// GetScale gets sprites size
	GetScale() Scale
	// SetRotate sets sprite's rotation
	SetRotate(r float32)
	// getRotate gets sprite's rotation
	GetRotate() float32
}

type Position struct {
	X, Y int
}
type Scale struct {
	W, H int
}

// Sprite represents a sprite object.
type sprite struct {
	peer.Sprite
	animationSets   map[string]*AnimationSet
	animationCancel func()
	texture         *Texture
}

// NewSprite returns an instance of Sprite
func NewSprite() Spriter {
	return &sprite{animationSets: map[string]*AnimationSet{}}
}

// ReplaceTexture replaces sprite's texture with specified image resource.
func (sprite *sprite) ReplaceTexture(texture *Texture) {
	LogDebug("IN")
	// retain reference for texture to avoid to be discarded by GC
	sprite.texture = texture
	peer.GetSpriteContainer().ReplaceTexture(&sprite.Sprite, texture.texture)
	LogDebug("OUT")
}

// AddTouchListener registers a listener for touch event.
// Touch event will be notified when "sprite" is touched.
func (sprite *sprite) AddTouchListener(listener peer.TouchListener) {
	LogDebug("IN")
	sprite.Sprite.AddTouchListener(listener)
	LogDebug("OUT")
}

// RemoveAllTouchListener removes all listeners already registered.
func (sprite *sprite) RemoveAllTouchListener() {
	LogDebug("IN")
	sprite.Sprite.RemoveAllTouchListener()
	LogDebug("OUT")
}

// AddAnimationSet adds a specified AnimationSet to sprite
func (sprite *sprite) AddAnimationSet(animationName string, set *AnimationSet) {
	LogDebug("IN")
	sprite.animationSets[animationName] = set
	LogDebug("OUT")
}

// StartAnimation starts animation by specified animation name
func (sprite *sprite) StartAnimation(animationName string, shouldLoop bool, animationEndCallback func()) {
	LogDebug("IN")
	if sprite.animationCancel != nil {
		// animation is already in progress. don't start.
		// TODO: should exlude control
		return
	}
	ctx := context.Background()
	ctx, cancel := context.WithCancel(ctx)
	sprite.animationCancel = cancel
	go sprite.startAnimation(ctx, animationName, shouldLoop, animationEndCallback)
	LogDebug("OUT")
}

func (sprite *sprite) startAnimation(ctx context.Context, animationName string, shouldLoop bool, animationEndCallback func()) {
	LogDebug("IN")
	animationSet := sprite.animationSets[animationName]
	if animationSet == nil {
		panic("specified animation is not set. animation name = " + animationName)
	}

	loopCount := 0
animation:
	for {
		select {
		case <-ctx.Done():
			break animation
		case <-fps.After(animationSet.interval):
			sprite.ReplaceTexture(animationSet.textures[loopCount])
			loopCount = (loopCount + 1) % len(animationSet.textures)
			if !shouldLoop && loopCount == 0 {
				break animation
			}
		}
	}
	sprite.animationCancel = nil
	animationEndCallback()
	LogDebug("OUT")
}

// StopAnimation stops animation
func (sprite *sprite) StopAnimation() {
	LogDebug("IN")
	if sprite == nil {
		return
	}

	if sprite.animationCancel != nil {
		sprite.animationCancel()
	}
	LogDebug("OUT")
}

// ProgressAnimation progresses registered animation
func (sprite *sprite) ProgressAnimation() {
	fps.Progress()
}

func (sprite *sprite) SetPosition(x, y int) {
	sprite.X, sprite.Y = (float32)(x), (float32)(y)
}

func (sprite *sprite) SetPositionX(x int) {
	sprite.X = (float32)(x)
}

func (sprite *sprite) SetPositionY(y int) {
	sprite.Y = (float32)(y)
}

func (sprite *sprite) SetScale(w, h int) {
	sprite.W, sprite.H = (float32)(w), (float32)(h)
}

func (sprite *sprite) SetScaleW(w int) {
	sprite.W = (float32)(w)
}

func (sprite *sprite) SetScaleH(h int) {
	sprite.H = (float32)(h)
}

func (sprite *sprite) GetPosition() Position {
	return Position{(int)(sprite.X), (int)(sprite.Y)}
}

func (sprite *sprite) GetScale() Scale {
	return Scale{(int)(sprite.W), (int)(sprite.H)}
}

func (sprite *sprite) SetRotate(r float32) {
	sprite.R = r
}

func (sprite *sprite) GetRotate() float32 {
	return sprite.R
}
<commit_msg>add comments on exported structs<commit_after>package simra

import (
	"context"

	"github.com/pankona/gomo-simra/simra/fps"
	"github.com/pankona/gomo-simra/simra/peer"
)

// Spriter represents an interface of Sprite
type Spriter interface {
	// ReplaceTexture replaces sprite's texture with specified image resource.
	ReplaceTexture(texture *Texture)
	// AddTouchListener registers a listener for touch event.
	// Touch event will be notified when "sprite" is touched.
	AddTouchListener(listener peer.TouchListener)
	// RemoveAllTouchListener removes all listeners already registered.
	RemoveAllTouchListener()
	// AddAnimationSet adds a specified AnimationSet to sprite
	AddAnimationSet(animationName string, set *AnimationSet)
	// StartAnimation starts animation by specified animation name
	StartAnimation(animationName string, shouldLoop bool, animationEndCallback func())
	// StopAnimation stops animation
	StopAnimation()
	// ProgressAnimation progresses registered animation
	ProgressAnimation()
	// SetPosition sets sprite's position
	SetPosition(x, y int)
	// SetPositionX sets sprite's position X
	SetPositionX(x int)
	// SetPositionY sets sprite's position Y
	SetPositionY(y int)
	// SetScale sets sprite's size
	SetScale(w, h int)
	// SetScaleW sets sprite's size W
	SetScaleW(w int)
	// SetScaleH sets sprite's size H
	SetScaleH(h int)
	// GetPosition gets sprites position
	GetPosition() Position
	// GetScale gets sprites size
	GetScale() Scale
	// SetRotate sets sprite's rotation
	SetRotate(r float32)
	// getRotate gets sprite's rotation
	GetRotate() float32
}

// Position represents position of sprite
type Position struct {
	X, Y int
}

// Scale represents size of sprite
type Scale struct {
	W, H int
}

// Sprite represents a sprite object.
type sprite struct {
	peer.Sprite
	animationSets   map[string]*AnimationSet
	animationCancel func()
	texture         *Texture
}

// NewSprite returns an instance of Sprite
func NewSprite() Spriter {
	return &sprite{animationSets: map[string]*AnimationSet{}}
}

// ReplaceTexture replaces sprite's texture with specified image resource.
func (sprite *sprite) ReplaceTexture(texture *Texture) {
	LogDebug("IN")
	// retain reference for texture to avoid to be discarded by GC
	sprite.texture = texture
	peer.GetSpriteContainer().ReplaceTexture(&sprite.Sprite, texture.texture)
	LogDebug("OUT")
}

// AddTouchListener registers a listener for touch event.
// Touch event will be notified when "sprite" is touched.
func (sprite *sprite) AddTouchListener(listener peer.TouchListener) {
	LogDebug("IN")
	sprite.Sprite.AddTouchListener(listener)
	LogDebug("OUT")
}

// RemoveAllTouchListener removes all listeners already registered.
func (sprite *sprite) RemoveAllTouchListener() {
	LogDebug("IN")
	sprite.Sprite.RemoveAllTouchListener()
	LogDebug("OUT")
}

// AddAnimationSet adds a specified AnimationSet to sprite
func (sprite *sprite) AddAnimationSet(animationName string, set *AnimationSet) {
	LogDebug("IN")
	sprite.animationSets[animationName] = set
	LogDebug("OUT")
}

// StartAnimation starts animation by specified animation name
func (sprite *sprite) StartAnimation(animationName string, shouldLoop bool, animationEndCallback func()) {
	LogDebug("IN")
	if sprite.animationCancel != nil {
		// animation is already in progress. don't start.
		// TODO: should exlude control
		return
	}
	ctx := context.Background()
	ctx, cancel := context.WithCancel(ctx)
	sprite.animationCancel = cancel
	go sprite.startAnimation(ctx, animationName, shouldLoop, animationEndCallback)
	LogDebug("OUT")
}

func (sprite *sprite) startAnimation(ctx context.Context, animationName string, shouldLoop bool, animationEndCallback func()) {
	LogDebug("IN")
	animationSet := sprite.animationSets[animationName]
	if animationSet == nil {
		panic("specified animation is not set. animation name = " + animationName)
	}

	loopCount := 0
animation:
	for {
		select {
		case <-ctx.Done():
			break animation
		case <-fps.After(animationSet.interval):
			sprite.ReplaceTexture(animationSet.textures[loopCount])
			loopCount = (loopCount + 1) % len(animationSet.textures)
			if !shouldLoop && loopCount == 0 {
				break animation
			}
		}
	}
	sprite.animationCancel = nil
	animationEndCallback()
	LogDebug("OUT")
}

// StopAnimation stops animation
func (sprite *sprite) StopAnimation() {
	LogDebug("IN")
	if sprite == nil {
		return
	}

	if sprite.animationCancel != nil {
		sprite.animationCancel()
	}
	LogDebug("OUT")
}

// ProgressAnimation progresses registered animation
func (sprite *sprite) ProgressAnimation() {
	fps.Progress()
}

func (sprite *sprite) SetPosition(x, y int) {
	sprite.X, sprite.Y = (float32)(x), (float32)(y)
}

func (sprite *sprite) SetPositionX(x int) {
	sprite.X = (float32)(x)
}

func (sprite *sprite) SetPositionY(y int) {
	sprite.Y = (float32)(y)
}

func (sprite *sprite) SetScale(w, h int) {
	sprite.W, sprite.H = (float32)(w), (float32)(h)
}

func (sprite *sprite) SetScaleW(w int) {
	sprite.W = (float32)(w)
}

func (sprite *sprite) SetScaleH(h int) {
	sprite.H = (float32)(h)
}

func (sprite *sprite) GetPosition() Position {
	return Position{(int)(sprite.X), (int)(sprite.Y)}
}

func (sprite *sprite) GetScale() Scale {
	return Scale{(int)(sprite.W), (int)(sprite.H)}
}

func (sprite *sprite) SetRotate(r float32) {
	sprite.R = r
}

func (sprite *sprite) GetRotate() float32 {
	return sprite.R
}
<|endoftext|>


<commit_before>// +build integ
// Copyright Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package pilot

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"testing"
	"time"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"istio.io/client-go/pkg/apis/networking/v1alpha3"
	"istio.io/istio/pilot/pkg/controller/workloadentry"
	"istio.io/istio/pilot/pkg/features"
	"istio.io/istio/pkg/test/framework"
	"istio.io/istio/pkg/test/framework/components/echo"
	echocommon "istio.io/istio/pkg/test/framework/components/echo/common"
	"istio.io/istio/pkg/test/framework/components/echo/echoboot"
	"istio.io/istio/pkg/test/framework/components/echo/kube"
	"istio.io/istio/pkg/test/framework/label"
	"istio.io/istio/pkg/test/scopes"
	"istio.io/istio/pkg/test/util/retry"
	"istio.io/istio/tests/integration/pilot/common"
)

func GetAdditionVMImages() []string {
	out := []echo.VMDistro{}
	for distro, image := range kube.VMImages {
		if distro == echo.DefaultVMDistro {
			continue
		}
		out = append(out, image)
	}
	return out
}

func TestVmOSPost(t *testing.T) {
	framework.
		NewTest(t).
		Features("traffic.reachability").
		Label(label.Postsubmit).
		Run(func(t framework.TestContext) {
			if t.Settings().SkipVM {
				t.Skip("VM tests are disabled")
			}
			b := echoboot.NewBuilder(t, t.Clusters().Primaries().Default())
			images := GetAdditionVMImages()
			for _, image := range images {
				b = b.WithConfig(echo.Config{
					Service:    "vm-" + strings.ReplaceAll(image, "_", "-"),
					Namespace:  apps.Namespace,
					Ports:      echocommon.EchoPorts,
					DeployAsVM: true,
					VMDistro:   image,
					Subsets:    []echo.SubsetConfig{{}},
				})
			}
			instances := b.BuildOrFail(t)

			for i, image := range images {
				i, image := i, image
				t.NewSubTest(image).RunParallel(func(t framework.TestContext) {
					for _, tt := range common.VMTestCases(echo.Instances{instances[i]}, apps) {
						tt.Run(t, apps.Namespace.Name())
					}
				})
			}
		})
}

func TestVMRegistrationLifecycle(t *testing.T) {
	framework.
		NewTest(t).
		RequiresSingleCluster().
		Features("vm.autoregistration").
		Run(func(t framework.TestContext) {
			if t.Settings().SkipVM {
				t.Skip()
			}
			scaleDeploymentOrFail(t, "istiod", i.Settings().SystemNamespace, 2)
			client := apps.PodA.GetOrFail(t, echo.InCluster(t.Clusters().Default()))
			// TODO test multi-network (must be shared control plane but on different networks)
			var autoVM echo.Instance
			_ = echoboot.NewBuilder(t).
				With(&autoVM, echo.Config{
					Namespace:      apps.Namespace,
					Service:        "auto-vm",
					Ports:          echocommon.EchoPorts,
					DeployAsVM:     true,
					AutoRegisterVM: true,
				}).BuildOrFail(t)
			t.NewSubTest("initial registration").Run(func(t framework.TestContext) {
				retry.UntilSuccessOrFail(t, func() error {
					res, err := client.Call(echo.CallOptions{Target: autoVM, Port: &autoVM.Config().Ports[0]})
					if err != nil {
						return err
					}
					return res.CheckOK()
				}, retry.Timeout(15*time.Second))
			})
			t.NewSubTest("reconnect reuses WorkloadEntry").Run(func(t framework.TestContext) {
				// ensure we have two pilot instances, other tests can pass before the second one comes up
				retry.UntilSuccessOrFail(t, func() error {
					pilotRes, err := t.Clusters().Default().CoreV1().Pods(i.Settings().SystemNamespace).
						List(context.TODO(), metav1.ListOptions{LabelSelector: "istio=pilot"})
					if err != nil {
						return err
					}
					if len(pilotRes.Items) != 2 {
						return errors.New("expected 2 pilots")
					}
					return nil
				}, retry.Timeout(10*time.Second))

				// get the initial workload entry state
				entries := getWorkloadEntriesOrFail(t, autoVM)
				if len(entries) != 1 {
					t.Fatalf("expected exactly 1 WorkloadEntry but got %d", len(entries))
				}
				initialWLE := entries[0]

				// keep force-disconnecting until we observe a reconnect to a different istiod instance
				initialPilot := initialWLE.Annotations[workloadentry.WorkloadControllerAnnotation]
				disconnectProxy(t, initialPilot, autoVM)
				retry.UntilSuccessOrFail(t, func() error {
					entries := getWorkloadEntriesOrFail(t, autoVM)
					if len(entries) != 1 || entries[0].UID != initialWLE.UID {
						t.Fatalf("WorkloadEntry was cleaned up unexpectedly")
					}

					currentPilot := entries[0].Annotations[workloadentry.WorkloadControllerAnnotation]
					if currentPilot == initialPilot || !strings.HasPrefix(currentPilot, "istiod-") {
						disconnectProxy(t, currentPilot, autoVM)
						return errors.New("expected WorkloadEntry to be updated by other pilot")
					}
					return nil
				}, retry.Delay(5*time.Second))
			})
			t.NewSubTest("disconnect deletes WorkloadEntry").Run(func(t framework.TestContext) {
				deployment := fmt.Sprintf("%s-%s", autoVM.Config().Service, "v1")
				scaleDeploymentOrFail(t, deployment, autoVM.Config().Namespace.Name(), 0)
				// it should take at most just over GracePeriod to cleanup if all pilots are healthy
				retry.UntilSuccessOrFail(t, func() error {
					if len(getWorkloadEntriesOrFail(t, autoVM)) > 0 {
						return errors.New("expected 0 WorkloadEntries")
					}
					return nil
				}, retry.Timeout(2*features.WorkloadEntryCleanupGracePeriod+(2*time.Second)))
			})
		})
}

func disconnectProxy(t framework.TestContext, pilot string, instance echo.Instance) {
	proxyID := strings.Join([]string{instance.WorkloadsOrFail(t)[0].PodName(), instance.Config().Namespace.Name()}, ".")
	cmd := "pilot-discovery request GET /debug/force_disconnect?proxyID=" + proxyID
	stdOut, _, err := t.Clusters().Default().
		PodExec(pilot, i.Settings().SystemNamespace, "discovery", cmd)
	if err != nil {
		scopes.Framework.Warnf("failed to force disconnect %s: %v: %v", proxyID, stdOut, err)
	}
}

func scaleDeploymentOrFail(t framework.TestContext, name, namespace string, scale int32) {
	s, err := t.Clusters().Default().AppsV1().Deployments(namespace).
		GetScale(context.TODO(), name, metav1.GetOptions{})
	if err != nil {
		t.Fatal(err)
	}
	s.Spec.Replicas = scale
	_, err = t.Clusters().Default().AppsV1().Deployments(namespace).
		UpdateScale(context.TODO(), name, s, metav1.UpdateOptions{})
	if err != nil {
		t.Fatal(err)
	}
}

func getWorkloadEntriesOrFail(t framework.TestContext, vm echo.Instance) []v1alpha3.WorkloadEntry {
	res, err := t.Clusters().Default().Istio().NetworkingV1alpha3().
		WorkloadEntries(vm.Config().Namespace.Name()).
		List(context.TODO(), metav1.ListOptions{LabelSelector: "app=" + vm.Config().Service})
	if err != nil {
		t.Fatal(err)
	}
	return res.Items
}
<commit_msg>Skip flaky VM test that depends on Kubernetes being slow (#33155)<commit_after>// +build integ
// Copyright Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package pilot

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"testing"
	"time"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"istio.io/client-go/pkg/apis/networking/v1alpha3"
	"istio.io/istio/pilot/pkg/controller/workloadentry"
	"istio.io/istio/pilot/pkg/features"
	"istio.io/istio/pkg/test/framework"
	"istio.io/istio/pkg/test/framework/components/echo"
	echocommon "istio.io/istio/pkg/test/framework/components/echo/common"
	"istio.io/istio/pkg/test/framework/components/echo/echoboot"
	"istio.io/istio/pkg/test/framework/components/echo/kube"
	"istio.io/istio/pkg/test/framework/label"
	"istio.io/istio/pkg/test/scopes"
	"istio.io/istio/pkg/test/util/retry"
	"istio.io/istio/tests/integration/pilot/common"
)

func GetAdditionVMImages() []string {
	out := []echo.VMDistro{}
	for distro, image := range kube.VMImages {
		if distro == echo.DefaultVMDistro {
			continue
		}
		out = append(out, image)
	}
	return out
}

func TestVmOSPost(t *testing.T) {
	framework.
		NewTest(t).
		Features("traffic.reachability").
		Label(label.Postsubmit).
		Run(func(t framework.TestContext) {
			if t.Settings().SkipVM {
				t.Skip("VM tests are disabled")
			}
			b := echoboot.NewBuilder(t, t.Clusters().Primaries().Default())
			images := GetAdditionVMImages()
			for _, image := range images {
				b = b.WithConfig(echo.Config{
					Service:    "vm-" + strings.ReplaceAll(image, "_", "-"),
					Namespace:  apps.Namespace,
					Ports:      echocommon.EchoPorts,
					DeployAsVM: true,
					VMDistro:   image,
					Subsets:    []echo.SubsetConfig{{}},
				})
			}
			instances := b.BuildOrFail(t)

			for i, image := range images {
				i, image := i, image
				t.NewSubTest(image).RunParallel(func(t framework.TestContext) {
					for _, tt := range common.VMTestCases(echo.Instances{instances[i]}, apps) {
						tt.Run(t, apps.Namespace.Name())
					}
				})
			}
		})
}

func TestVMRegistrationLifecycle(t *testing.T) {
	t.Skip("https://github.com/istio/istio/issues/33154")
	framework.
		NewTest(t).
		RequiresSingleCluster().
		Features("vm.autoregistration").
		Run(func(t framework.TestContext) {
			if t.Settings().SkipVM {
				t.Skip()
			}
			scaleDeploymentOrFail(t, "istiod", i.Settings().SystemNamespace, 2)
			client := apps.PodA.GetOrFail(t, echo.InCluster(t.Clusters().Default()))
			// TODO test multi-network (must be shared control plane but on different networks)
			var autoVM echo.Instance
			_ = echoboot.NewBuilder(t).
				With(&autoVM, echo.Config{
					Namespace:      apps.Namespace,
					Service:        "auto-vm",
					Ports:          echocommon.EchoPorts,
					DeployAsVM:     true,
					AutoRegisterVM: true,
				}).BuildOrFail(t)
			t.NewSubTest("initial registration").Run(func(t framework.TestContext) {
				retry.UntilSuccessOrFail(t, func() error {
					res, err := client.Call(echo.CallOptions{Target: autoVM, Port: &autoVM.Config().Ports[0]})
					if err != nil {
						return err
					}
					return res.CheckOK()
				}, retry.Timeout(15*time.Second))
			})
			t.NewSubTest("reconnect reuses WorkloadEntry").Run(func(t framework.TestContext) {
				// ensure we have two pilot instances, other tests can pass before the second one comes up
				retry.UntilSuccessOrFail(t, func() error {
					pilotRes, err := t.Clusters().Default().CoreV1().Pods(i.Settings().SystemNamespace).
						List(context.TODO(), metav1.ListOptions{LabelSelector: "istio=pilot"})
					if err != nil {
						return err
					}
					if len(pilotRes.Items) != 2 {
						return errors.New("expected 2 pilots")
					}
					return nil
				}, retry.Timeout(10*time.Second))

				// get the initial workload entry state
				entries := getWorkloadEntriesOrFail(t, autoVM)
				if len(entries) != 1 {
					t.Fatalf("expected exactly 1 WorkloadEntry but got %d", len(entries))
				}
				initialWLE := entries[0]

				// keep force-disconnecting until we observe a reconnect to a different istiod instance
				initialPilot := initialWLE.Annotations[workloadentry.WorkloadControllerAnnotation]
				disconnectProxy(t, initialPilot, autoVM)
				retry.UntilSuccessOrFail(t, func() error {
					entries := getWorkloadEntriesOrFail(t, autoVM)
					if len(entries) != 1 || entries[0].UID != initialWLE.UID {
						t.Fatalf("WorkloadEntry was cleaned up unexpectedly")
					}

					currentPilot := entries[0].Annotations[workloadentry.WorkloadControllerAnnotation]
					if currentPilot == initialPilot || !strings.HasPrefix(currentPilot, "istiod-") {
						disconnectProxy(t, currentPilot, autoVM)
						return errors.New("expected WorkloadEntry to be updated by other pilot")
					}
					return nil
				}, retry.Delay(5*time.Second))
			})
			t.NewSubTest("disconnect deletes WorkloadEntry").Run(func(t framework.TestContext) {
				deployment := fmt.Sprintf("%s-%s", autoVM.Config().Service, "v1")
				scaleDeploymentOrFail(t, deployment, autoVM.Config().Namespace.Name(), 0)
				// it should take at most just over GracePeriod to cleanup if all pilots are healthy
				retry.UntilSuccessOrFail(t, func() error {
					if len(getWorkloadEntriesOrFail(t, autoVM)) > 0 {
						return errors.New("expected 0 WorkloadEntries")
					}
					return nil
				}, retry.Timeout(2*features.WorkloadEntryCleanupGracePeriod+(2*time.Second)))
			})
		})
}

func disconnectProxy(t framework.TestContext, pilot string, instance echo.Instance) {
	proxyID := strings.Join([]string{instance.WorkloadsOrFail(t)[0].PodName(), instance.Config().Namespace.Name()}, ".")
	cmd := "pilot-discovery request GET /debug/force_disconnect?proxyID=" + proxyID
	stdOut, _, err := t.Clusters().Default().
		PodExec(pilot, i.Settings().SystemNamespace, "discovery", cmd)
	if err != nil {
		scopes.Framework.Warnf("failed to force disconnect %s: %v: %v", proxyID, stdOut, err)
	}
}

func scaleDeploymentOrFail(t framework.TestContext, name, namespace string, scale int32) {
	s, err := t.Clusters().Default().AppsV1().Deployments(namespace).
		GetScale(context.TODO(), name, metav1.GetOptions{})
	if err != nil {
		t.Fatal(err)
	}
	s.Spec.Replicas = scale
	_, err = t.Clusters().Default().AppsV1().Deployments(namespace).
		UpdateScale(context.TODO(), name, s, metav1.UpdateOptions{})
	if err != nil {
		t.Fatal(err)
	}
}

func getWorkloadEntriesOrFail(t framework.TestContext, vm echo.Instance) []v1alpha3.WorkloadEntry {
	res, err := t.Clusters().Default().Istio().NetworkingV1alpha3().
		WorkloadEntries(vm.Config().Namespace.Name()).
		List(context.TODO(), metav1.ListOptions{LabelSelector: "app=" + vm.Config().Service})
	if err != nil {
		t.Fatal(err)
	}
	return res.Items
}
<|endoftext|>


<commit_before>package cli_test

import (
	"testing"

	"github.com/mehrdadrad/mylg/cli"
)

var c *cli.Readline

func TestInitPrompt(t *testing.T) {
	c = cli.Init("local", "test")
	p := c.GetPrompt()
	if p != "local" {
		t.Error("Init cli prompt failed")
	}
}

func TestSetPrompt(t *testing.T) {
	c = cli.Init("local", "test")
	c.SetPrompt("mylg")
	p := c.GetPrompt()
	if p != "mylg" {
		t.Error("Set cli prompt failed")
	}
}
<commit_msg>added flag test<commit_after>package cli_test

import (
	"testing"

	"github.com/mehrdadrad/mylg/cli"
)

var c *cli.Readline

func TestInitPrompt(t *testing.T) {
	c = cli.Init("local", "test")
	p := c.GetPrompt()
	if p != "local" {
		t.Error("Init cli prompt failed")
	}
}

func TestSetPrompt(t *testing.T) {
	c = cli.Init("local", "test")
	c.SetPrompt("mylg")
	p := c.GetPrompt()
	if p != "mylg" {
		t.Error("Set cli prompt failed")
	}
}

func TestFlag(t *testing.T) {
	// test help
	url, flag := cli.Flag("www.mylg.io help")
	if _, ok := flag["help"]; !ok {
		t.Error("flag expected help request but not exist")
	}
	// test simple arg
	url, flag = cli.Flag("www.mylg.io")
	if _, ok := flag["help"]; ok {
		t.Error("flag expected none help")
	}
	if url != "www.mylg.io" {
		t.Error("flag unexpected url")
	}
	// test option and string value
	url, flag = cli.Flag("www.mylg.io -option=value")
	if _, ok := flag["option"]; !ok {
		t.Error("flag unexpected option")
	} else if flag["option"] != "value" {
		t.Error("flag unexpected string value")
	}
	if url != "www.mylg.io" {
		t.Error("")
		println("url", url)
	}
	// test option and int value
	url, flag = cli.Flag("www.mylg.io -option=1976")
	if _, ok := flag["option"]; !ok {
		t.Error("flag unexpected option")
	} else if flag["option"] != 1976 {
		t.Error("flag unexpected int value")
	}
	if url != "www.mylg.io" {
		t.Error("")
		println("url", url)
	}

}
<|endoftext|>


<commit_before>package middleware

import (
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/labstack/echo"
	"github.com/stretchr/testify/assert"
)

func TestRedirectHTTPSRedirect(t *testing.T) {
	e := echo.New()
	next := func(c echo.Context) (err error) {
		return c.NoContent(http.StatusOK)
	}
	req := httptest.NewRequest(echo.GET, "/", nil)
	req.Host = "labstack.com"
	res := httptest.NewRecorder()
	c := e.NewContext(req, res)
	HTTPSRedirect()(next)(c)
	assert.Equal(t, http.StatusMovedPermanently, res.Code)
	assert.Equal(t, "https://labstack.com/", res.Header().Get(echo.HeaderLocation))
}

func TestRedirectHTTPSWWWRedirect(t *testing.T) {
	e := echo.New()
	next := func(c echo.Context) (err error) {
		return c.NoContent(http.StatusOK)
	}
	req := httptest.NewRequest(echo.GET, "/", nil)
	req.Host = "labstack.com"
	res := httptest.NewRecorder()
	c := e.NewContext(req, res)
	HTTPSWWWRedirect()(next)(c)
	assert.Equal(t, http.StatusMovedPermanently, res.Code)
	assert.Equal(t, "https://www.labstack.com/", res.Header().Get(echo.HeaderLocation))
}

func TestRedirectHTTPSNonWWWRedirect(t *testing.T) {
	e := echo.New()
	next := func(c echo.Context) (err error) {
		return c.NoContent(http.StatusOK)
	}
	req := httptest.NewRequest(echo.GET, "/", nil)
	req.Host = "www.labstack.com"
	res := httptest.NewRecorder()
	c := e.NewContext(req, res)
	HTTPSNonWWWRedirect()(next)(c)
	assert.Equal(t, http.StatusMovedPermanently, res.Code)
	assert.Equal(t, "https://labstack.com/", res.Header().Get(echo.HeaderLocation))
}

func TestRedirectWWWRedirect(t *testing.T) {
	e := echo.New()
	next := func(c echo.Context) (err error) {
		return c.NoContent(http.StatusOK)
	}
	req := httptest.NewRequest(echo.GET, "/", nil)
	req.Host = "labstack.com"
	res := httptest.NewRecorder()
	c := e.NewContext(req, res)
	WWWRedirect()(next)(c)
	assert.Equal(t, http.StatusMovedPermanently, res.Code)
	assert.Equal(t, "http://www.labstack.com/", res.Header().Get(echo.HeaderLocation))
}

func TestRedirectNonWWWRedirect(t *testing.T) {
	e := echo.New()
	next := func(c echo.Context) (err error) {
		return c.NoContent(http.StatusOK)
	}
	req := httptest.NewRequest(echo.GET, "/", nil)
	req.Host = "www.labstack.com"
	res := httptest.NewRecorder()
	c := e.NewContext(req, res)
	NonWWWRedirect()(next)(c)
	assert.Equal(t, http.StatusMovedPermanently, res.Code)
	assert.Equal(t, "http://labstack.com/", res.Header().Get(echo.HeaderLocation))
}
<commit_msg>DRY'ed the redirect middleware tests a little (#1055)<commit_after>package middleware

import (
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/labstack/echo"
	"github.com/stretchr/testify/assert"
)

type middlewareGenerator func() echo.MiddlewareFunc

func TestRedirectHTTPSRedirect(t *testing.T) {
	res := redirectTest(HTTPSRedirect, "labstack.com")

	assert.Equal(t, http.StatusMovedPermanently, res.Code)
	assert.Equal(t, "https://labstack.com/", res.Header().Get(echo.HeaderLocation))
}

func TestRedirectHTTPSWWWRedirect(t *testing.T) {
	res := redirectTest(HTTPSWWWRedirect, "labstack.com")

	assert.Equal(t, http.StatusMovedPermanently, res.Code)
	assert.Equal(t, "https://www.labstack.com/", res.Header().Get(echo.HeaderLocation))
}

func TestRedirectHTTPSNonWWWRedirect(t *testing.T) {
	res := redirectTest(HTTPSNonWWWRedirect, "www.labstack.com")

	assert.Equal(t, http.StatusMovedPermanently, res.Code)
	assert.Equal(t, "https://labstack.com/", res.Header().Get(echo.HeaderLocation))
}

func TestRedirectWWWRedirect(t *testing.T) {
	res := redirectTest(WWWRedirect, "labstack.com")

	assert.Equal(t, http.StatusMovedPermanently, res.Code)
	assert.Equal(t, "http://www.labstack.com/", res.Header().Get(echo.HeaderLocation))
}

func TestRedirectNonWWWRedirect(t *testing.T) {
	res := redirectTest(NonWWWRedirect, "www.labstack.com")

	assert.Equal(t, http.StatusMovedPermanently, res.Code)
	assert.Equal(t, "http://labstack.com/", res.Header().Get(echo.HeaderLocation))
}

func redirectTest(fn middlewareGenerator, host string) *httptest.ResponseRecorder {
	e := echo.New()
	next := func(c echo.Context) (err error) {
		return c.NoContent(http.StatusOK)
	}
	req := httptest.NewRequest(echo.GET, "/", nil)
	req.Host = host
	res := httptest.NewRecorder()
	c := e.NewContext(req, res)

	fn()(next)(c)

	return res
}
<|endoftext|>