<commit_before>// Copyright 2014 Volker Dobler.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// json.go contains checks for a JSON body.

package ht

import (
	"encoding/json"
	"fmt"
	"strconv"

	"github.com/nytlabs/gojee"
	"github.com/nytlabs/gojsonexplode"
)

func init() {
	RegisterCheck(&JSONExpr{})
	RegisterCheck(&JSON{})
}

// ----------------------------------------------------------------------------
// JSONExpr

// JSONExpr allows checking JSON documents via gojee expressions.
// See github.com/nytlabs/gojee (or the vendored version) for details.
//
// Consider this JSON:
//     { "foo": 5, "bar": [ 1, 2, 3 ] }
// The follwing expression have these truth values:
//     .foo == 5                    true
//     $len(.bar) > 2               true as $len(.bar)==3
//     .bar[1] == 2                 true
//     (.foo == 9) || (.bar[0]<7)   true as .bar[0]==1
//     $max(.bar) == 3              true
//     $has(.bar, 7)                false as bar has no 7
type JSONExpr struct {
	// Expression is a boolean gojee expression which must evaluate
	// to true for the check to pass.
	Expression string `json:",omitempty"`

	tt *jee.TokenTree
}

// Prepare implements Check's Prepare method.
func (c *JSONExpr) Prepare() (err error) {
	if c.Expression == "" {
		return fmt.Errorf("Expression must not be empty")
	}

	tokens, err := jee.Lexer(c.Expression)
	if err != nil {
		return err
	}
	c.tt, err = jee.Parser(tokens)
	if err != nil {
		return err
	}

	return nil
}

// Execute implements Check's Execute method.
func (c *JSONExpr) Execute(t *Test) error {
	if t.Response.BodyErr != nil {
		return ErrBadBody
	}

	if c.tt == nil {
		if err := c.Prepare(); err != nil {
			return MalformedCheck{Err: err}
		}
	}

	var bmsg jee.BMsg
	err := json.Unmarshal([]byte(t.Response.BodyStr), &bmsg)
	if err != nil {
		return err
	}

	result, err := jee.Eval(c.tt, bmsg)
	if err != nil {
		return err
	}

	if b, ok := result.(bool); !ok {
		return MalformedCheck{Err: fmt.Errorf("Expected bool, got %T (%#v)", result, result)}
	} else if !b {
		return ErrFailed
	}
	return nil
}

// ----------------------------------------------------------------------------
// JSON

// JSON allow to check a single string, number, boolean or null element in
// a JSON document against a Condition.
//
// Elements of the JSON document are selected by an element selector.
// In the JSON document
//     { "foo": 5, "bar": [ 1, "qux", 3 ], "waz": true, "nil": null }
// the follwing element selector are present and have the shown values:
//     foo       5
//     bar.0     1
//     bar.1     "qux"
//     bar.2     3
//     waz       true
//     nil       null
type JSON struct {
	// Element in the flattened JSON map to apply the Condition to.
	// E.g.  "foo.2" in "{foo: [4,5,6,7]}" would be 6.
	// An empty value result in just a check for 'wellformedness' of
	// the JSON.
	Element string

	// Condition to apply to the value selected by Element.
	// If Condition is the zero value then only the existence of
	// a JSON element selected by Element is checked.
	// Note that Condition is checked against the actual value in the
	// flattened JSON map which will contain the quotation marks for
	// string values.
	Condition

	// Embeded is a JSON check applied to the value selected by
	// Element. Useful when JSON contains embedded, quoted JSON as
	// a string and checking via Condition is not practical.
	// (It seems this nested JSON is common nowadays. I'm getting old.)
	Embedded *JSON

	// Sep is the separator in Element when checking the Condition.
	// A zero value is equivalent to "."
	Sep string `json:",omitempty"`
}

// Prepare implements Check's Prepare method.
func (c *JSON) Prepare() error {
	err := c.Compile()
	if err != nil {
		return err
	}
	if c.Embedded != nil {
		return c.Embedded.Prepare()
	}
	return nil
}

// Execute implements Check's Execute method.
func (c *JSON) Execute(t *Test) error {
	if t.Response.BodyErr != nil {
		return ErrBadBody
	}
	sep := "."
	if c.Sep != "" {
		sep = c.Sep
	}

	out, err := gojsonexplode.Explodejson([]byte(t.Response.BodyStr), sep)
	if err != nil {
		return fmt.Errorf("unable to explode JSON: %s", err.Error())
	}

	var flat map[string]*json.RawMessage
	err = json.Unmarshal(out, &flat)
	if err != nil {
		return fmt.Errorf("unable to parse exploded JSON: %s", err.Error())
	}
	if c.Element == "" && c.Embedded == nil {
		return nil // JSON was welformed, no further checks.
	}

	val, ok := flat[c.Element]
	if !ok {
		return fmt.Errorf("element %s not found", c.Element)
	}
	sval := "null"
	if val != nil {
		sval = string(*val)
	}

	if c.Embedded != nil {
		unquoted, err := strconv.Unquote(sval)
		if err != nil {
			return fmt.Errorf("element %s: %s", c.Element, err)
		}
		etest := &Test{Response: Response{BodyStr: unquoted}}
		eerr := c.Embedded.Execute(etest)
		if eerr != nil {
			return fmt.Errorf("embedded: %s", eerr)
		}
	}

	return c.Fulfilled(sval)
}
<commit_msg>ht: hidde an empty field in JSON check<commit_after>// Copyright 2014 Volker Dobler.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// json.go contains checks for a JSON body.

package ht

import (
	"encoding/json"
	"fmt"
	"strconv"

	"github.com/nytlabs/gojee"
	"github.com/nytlabs/gojsonexplode"
)

func init() {
	RegisterCheck(&JSONExpr{})
	RegisterCheck(&JSON{})
}

// ----------------------------------------------------------------------------
// JSONExpr

// JSONExpr allows checking JSON documents via gojee expressions.
// See github.com/nytlabs/gojee (or the vendored version) for details.
//
// Consider this JSON:
//     { "foo": 5, "bar": [ 1, 2, 3 ] }
// The follwing expression have these truth values:
//     .foo == 5                    true
//     $len(.bar) > 2               true as $len(.bar)==3
//     .bar[1] == 2                 true
//     (.foo == 9) || (.bar[0]<7)   true as .bar[0]==1
//     $max(.bar) == 3              true
//     $has(.bar, 7)                false as bar has no 7
type JSONExpr struct {
	// Expression is a boolean gojee expression which must evaluate
	// to true for the check to pass.
	Expression string `json:",omitempty"`

	tt *jee.TokenTree
}

// Prepare implements Check's Prepare method.
func (c *JSONExpr) Prepare() (err error) {
	if c.Expression == "" {
		return fmt.Errorf("Expression must not be empty")
	}

	tokens, err := jee.Lexer(c.Expression)
	if err != nil {
		return err
	}
	c.tt, err = jee.Parser(tokens)
	if err != nil {
		return err
	}

	return nil
}

// Execute implements Check's Execute method.
func (c *JSONExpr) Execute(t *Test) error {
	if t.Response.BodyErr != nil {
		return ErrBadBody
	}

	if c.tt == nil {
		if err := c.Prepare(); err != nil {
			return MalformedCheck{Err: err}
		}
	}

	var bmsg jee.BMsg
	err := json.Unmarshal([]byte(t.Response.BodyStr), &bmsg)
	if err != nil {
		return err
	}

	result, err := jee.Eval(c.tt, bmsg)
	if err != nil {
		return err
	}

	if b, ok := result.(bool); !ok {
		return MalformedCheck{Err: fmt.Errorf("Expected bool, got %T (%#v)", result, result)}
	} else if !b {
		return ErrFailed
	}
	return nil
}

// ----------------------------------------------------------------------------
// JSON

// JSON allow to check a single string, number, boolean or null element in
// a JSON document against a Condition.
//
// Elements of the JSON document are selected by an element selector.
// In the JSON document
//     { "foo": 5, "bar": [ 1, "qux", 3 ], "waz": true, "nil": null }
// the follwing element selector are present and have the shown values:
//     foo       5
//     bar.0     1
//     bar.1     "qux"
//     bar.2     3
//     waz       true
//     nil       null
type JSON struct {
	// Element in the flattened JSON map to apply the Condition to.
	// E.g.  "foo.2" in "{foo: [4,5,6,7]}" would be 6.
	// An empty value result in just a check for 'wellformedness' of
	// the JSON.
	Element string

	// Condition to apply to the value selected by Element.
	// If Condition is the zero value then only the existence of
	// a JSON element selected by Element is checked.
	// Note that Condition is checked against the actual value in the
	// flattened JSON map which will contain the quotation marks for
	// string values.
	Condition

	// Embeded is a JSON check applied to the value selected by
	// Element. Useful when JSON contains embedded, quoted JSON as
	// a string and checking via Condition is not practical.
	// (It seems this nested JSON is common nowadays. I'm getting old.)
	Embedded *JSON `json:",omitempty"`

	// Sep is the separator in Element when checking the Condition.
	// A zero value is equivalent to "."
	Sep string `json:",omitempty"`
}

// Prepare implements Check's Prepare method.
func (c *JSON) Prepare() error {
	err := c.Compile()
	if err != nil {
		return err
	}
	if c.Embedded != nil {
		return c.Embedded.Prepare()
	}
	return nil
}

// Execute implements Check's Execute method.
func (c *JSON) Execute(t *Test) error {
	if t.Response.BodyErr != nil {
		return ErrBadBody
	}
	sep := "."
	if c.Sep != "" {
		sep = c.Sep
	}

	out, err := gojsonexplode.Explodejson([]byte(t.Response.BodyStr), sep)
	if err != nil {
		return fmt.Errorf("unable to explode JSON: %s", err.Error())
	}

	var flat map[string]*json.RawMessage
	err = json.Unmarshal(out, &flat)
	if err != nil {
		return fmt.Errorf("unable to parse exploded JSON: %s", err.Error())
	}
	if c.Element == "" && c.Embedded == nil {
		return nil // JSON was welformed, no further checks.
	}

	val, ok := flat[c.Element]
	if !ok {
		return fmt.Errorf("element %s not found", c.Element)
	}
	sval := "null"
	if val != nil {
		sval = string(*val)
	}

	if c.Embedded != nil {
		unquoted, err := strconv.Unquote(sval)
		if err != nil {
			return fmt.Errorf("element %s: %s", c.Element, err)
		}
		etest := &Test{Response: Response{BodyStr: unquoted}}
		eerr := c.Embedded.Execute(etest)
		if eerr != nil {
			return fmt.Errorf("embedded: %s", eerr)
		}
	}

	return c.Fulfilled(sval)
}
<|endoftext|>


<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"

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

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) {
	// Avoid defer for performance
	q.m.Lock()
	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)
				q.m.Unlock()
				return
			}
		}
	}
	c := &drawImageCommand{
		dst:         dst,
		src:         src,
		verticesNum: len(vertices),
		color:       *clr,
		mode:        mode,
	}
	q.commands = append(q.commands, c)
	q.m.Unlock()
}

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

// 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
}
<commit_msg>graphics: Replace copy with for-loop<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"

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

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)...)
	}
	// for-loop might be faster than copy:
	// On GopherJS, copy might cause subarray calls.
	for i := 0; i < len(vertices); i++ {
		q.vertices[q.verticesNum+i] = vertices[i]
	}
	q.verticesNum += len(vertices)
}

func (q *commandQueue) EnqueueDrawImageCommand(dst, src *Image, vertices []float32, clr *affine.ColorM, mode opengl.CompositeMode) {
	// Avoid defer for performance
	q.m.Lock()
	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)
				q.m.Unlock()
				return
			}
		}
	}
	c := &drawImageCommand{
		dst:         dst,
		src:         src,
		verticesNum: len(vertices),
		color:       *clr,
		mode:        mode,
	}
	q.commands = append(q.commands, c)
	q.m.Unlock()
}

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

// 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 (c) 2016 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package marshaled

import (
	"fmt"
	"io"
	"log"
	"sort"
	"strings"
	"sync"
	"time"

	"github.com/uber-go/gwr/source"
)

// NOTE: This approach is perhaps overfit to the json module's marshalling
// mindset.  A better interface (for performance) would work by passing a
// writer to the specific encoder, rather than a []byte-returning Marshal
// function.  This would be possible perhaps using something like
// io.MultiWriter.

// DataSource wraps a format-agnostic data source and provides one or
// more formats for it.
//
// DataSource implements:
// - DataSource to satisfy DataSources and low level protocols
// - ItemDataSource so that higher level protocols may add their own framing
// - GenericDataWatcher inwardly to the wrapped GenericDataSource
type DataSource struct {
	// TODO: better to have alternate implementations for each combination
	// rather than one with these nil checks
	source      source.GenericDataSource
	getSource   source.GetableDataSource
	watchSource source.WatchableDataSource
	watiSource  source.WatchInitableDataSource
	actiSource  source.ActivateWatchableDataSource

	formats     map[string]source.GenericDataFormat
	formatNames []string
	maxItems    int
	maxBatches  int
	maxWait     time.Duration

	procs     sync.WaitGroup
	watchLock sync.Mutex
	watchers  map[string]*marshaledWatcher
	active    bool
	itemChan  chan interface{}
	itemsChan chan []interface{}
}

func stringIt(item interface{}) ([]byte, error) {
	var s string
	if ss, ok := item.(fmt.Stringer); ok {
		s = ss.String()
	} else {
		s = fmt.Sprintf("%+v", item)
	}
	return []byte(s), nil
}

// NewDataSource creates a DataSource for a given format-agnostic data source
// and a map of marshalers
func NewDataSource(
	src source.GenericDataSource,
	formats map[string]source.GenericDataFormat,
) *DataSource {
	if formats == nil {
		formats = make(map[string]source.GenericDataFormat)
	}

	// source-defined formats
	if fmtsrc, ok := src.(source.GenericDataSourceFormats); ok {
		fmts := fmtsrc.Formats()
		for name, fmt := range fmts {
			formats[name] = fmt
		}
	}

	// standard json protocol
	if formats["json"] == nil {
		formats["json"] = LDJSONMarshal
	}

	// convenience templated text protocol
	if formats["text"] == nil {
		if txtsrc, ok := src.(source.TextTemplatedSource); ok {
			if tt := txtsrc.TextTemplate(); tt != nil {
				formats["text"] = NewTemplatedMarshal(tt)
			}
		}
	}

	// default to just string-ing it
	if formats["text"] == nil {
		formats["text"] = source.GenericDataFormatFunc(stringIt)
	}

	ds := &DataSource{
		source:   src,
		formats:  formats,
		watchers: make(map[string]*marshaledWatcher, len(formats)),
		// TODO: tunable
		maxItems:   100,
		maxBatches: 100,
		maxWait:    100 * time.Microsecond,
	}
	ds.getSource, _ = src.(source.GetableDataSource)
	ds.watchSource, _ = src.(source.WatchableDataSource)
	ds.watiSource, _ = src.(source.WatchInitableDataSource)
	ds.actiSource, _ = src.(source.ActivateWatchableDataSource)
	for name, format := range formats {
		ds.formatNames = append(ds.formatNames, name)
		ds.watchers[name] = newMarshaledWatcher(ds, format)
	}
	sort.Strings(ds.formatNames)

	if ds.watchSource != nil {
		ds.watchSource.SetWatcher(ds)
	}

	return ds
}

// Active returns true if there are any active watchers, false otherwise.  If
// Active returns false, so will any calls to HandleItem and HandleItems.
func (mds *DataSource) Active() bool {
	mds.watchLock.Lock()
	r := mds.active && mds.itemChan != nil && mds.itemsChan != nil
	mds.watchLock.Unlock()
	return r
}

// Name passes through the GenericDataSource.Name()
func (mds *DataSource) Name() string {
	return mds.source.Name()
}

// Formats returns the list of supported format names.
func (mds *DataSource) Formats() []string {
	return mds.formatNames
}

// Attrs returns arbitrary description information about the data source.
func (mds *DataSource) Attrs() map[string]interface{} {
	// TODO: support per-format Attrs?
	// TODO: any support for per-source Attrs?
	return nil
}

// Get marshals data source's Get data to the writer
func (mds *DataSource) Get(formatName string, w io.Writer) error {
	if mds.getSource == nil {
		return source.ErrNotGetable
	}
	format, ok := mds.formats[strings.ToLower(formatName)]
	if !ok {
		return source.ErrUnsupportedFormat
	}
	data := mds.getSource.Get()
	buf, err := format.MarshalGet(data)
	if err != nil {
		log.Printf("get marshaling error %v", err)
		return err
	}
	_, err = w.Write(buf)
	return err
}

// Watch marshals any data source GetInit data to the writer, and then
// retains a reference to the writer so that any future agnostic data source
// Watch(emit)'ed data gets marshaled to it as well
func (mds *DataSource) Watch(formatName string, w io.Writer) error {
	if mds.watchSource == nil {
		return source.ErrNotWatchable
	}

	mds.watchLock.Lock()
	acted := !mds.active
	err := func() error {
		defer mds.watchLock.Unlock()
		watcher, ok := mds.watchers[strings.ToLower(formatName)]
		if !ok {
			return source.ErrUnsupportedFormat
		}
		if err := watcher.init(w); err != nil {
			return err
		}
		if err := mds.startWatching(); err != nil {
			return err
		}
		return nil
	}()

	if err == nil && acted && mds.actiSource != nil {
		mds.actiSource.Activate()
	}
	return err
}

// WatchItems marshals any data source GetInit data as a single item to the
// ItemWatcher's HandleItem method.  The watcher is then retained and future
// items are marshaled to its HandleItem method.
func (mds *DataSource) WatchItems(formatName string, iw source.ItemWatcher) error {
	if mds.watchSource == nil {
		return source.ErrNotWatchable
	}

	mds.watchLock.Lock()
	acted := !mds.active
	err := func() error {
		defer mds.watchLock.Unlock()
		watcher, ok := mds.watchers[strings.ToLower(formatName)]
		if !ok {
			return source.ErrUnsupportedFormat
		}
		if err := watcher.initItems(iw); err != nil {
			return err
		}
		if err := mds.startWatching(); err != nil {
			return err
		}
		return nil
	}()

	if err == nil && acted && mds.actiSource != nil {
		mds.actiSource.Activate()
	}
	return err
}

// startWatching flips the active bit, creates new item channels, and starts a
// processing go routine; it assumes that the watchLock is being held by the
// caller.
func (mds *DataSource) startWatching() error {
	// TODO: we could optimize the only-one-format-being-watched case
	if mds.active {
		return nil
	}
	mds.active = true
	mds.itemChan = make(chan interface{}, mds.maxItems)
	mds.itemsChan = make(chan []interface{}, mds.maxBatches)
	mds.procs.Add(1)
	go mds.processItemChan(mds.itemChan, mds.itemsChan)
	return nil
}

// Drain closes the item channels, and waits for the item processor to finish.
// After drain, any remaining watchers are closed, and the source goes
// inactive.
func (mds *DataSource) Drain() {
	mds.watchLock.Lock()
	any := false
	if mds.itemChan != nil {
		close(mds.itemChan)
		any = true
		mds.itemChan = nil
	}
	if mds.itemsChan != nil {
		close(mds.itemsChan)
		any = true
		mds.itemsChan = nil
	}
	if any {
		mds.watchLock.Unlock()
		mds.procs.Wait()
		mds.watchLock.Lock()
	}
	stop := mds.active
	if stop {
		mds.active = false
	}
	mds.watchLock.Unlock()

	if stop {
		for _, watcher := range mds.watchers {
			watcher.Close()
		}
	}
}

func (mds *DataSource) processItemChan(itemChan chan interface{}, itemsChan chan []interface{}) {
	defer mds.procs.Done()

	stop := false

loop:
	for {
		mds.watchLock.Lock()
		active := mds.active
		watchers := mds.watchers
		mds.watchLock.Unlock()
		if !active {
			break loop
		}
		select {
		case item, ok := <-itemChan:
			if !ok {
				itemChan = nil
				continue loop
			}
			any := false
			for _, watcher := range watchers {
				if watcher.emit(item) {
					any = true
				}
			}
			if !any {
				stop = true
				break loop
			}

		case items, ok := <-itemsChan:
			if !ok {
				itemsChan = nil
				continue loop
			}
			any := false
			for _, watcher := range watchers {
				if watcher.emitBatch(items) {
					any = true
				}
			}
			if !any {
				stop = true
				break loop
			}

		default:
			if itemChan == nil && itemsChan == nil {
				break loop
			}
		}
	}

	mds.watchLock.Lock()
	if mds.itemChan == itemChan {
		mds.itemChan = nil
	}
	if mds.itemsChan == itemsChan {
		mds.itemsChan = nil
	}
	if stop {
		mds.active = false
	}
	mds.watchLock.Unlock()

	if stop {
		for _, watcher := range mds.watchers {
			watcher.Close()
		}
	}
}

// HandleItem implements GenericDataWatcher.HandleItem by passing the item to
// all current marshaledWatchers.
func (mds *DataSource) HandleItem(item interface{}) bool {
	if !mds.Active() {
		return false
	}
	select {
	case mds.itemChan <- item:
		return true
	case <-time.After(mds.maxWait):
		mds.watchLock.Lock()
		if !mds.active {
			return false
		}
		mds.active = false
		mds.watchLock.Unlock()
		for _, watcher := range mds.watchers {
			watcher.Close()
		}
		return false
	}
}

// HandleItems implements GenericDataWatcher.HandleItems by passing the batch
// to all current marshaledWatchers.
func (mds *DataSource) HandleItems(items []interface{}) bool {
	if !mds.Active() {
		return false
	}
	select {
	case mds.itemsChan <- items:
		return true
	case <-time.After(mds.maxWait):
		mds.watchLock.Lock()
		if !mds.active {
			return false
		}
		mds.active = false
		mds.watchLock.Unlock()
		for _, watcher := range mds.watchers {
			watcher.Close()
		}
		return false
	}
}
<commit_msg>Fix potential deadlock in HandleItem(s) (#24)<commit_after>// Copyright (c) 2016 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package marshaled

import (
	"fmt"
	"io"
	"log"
	"sort"
	"strings"
	"sync"
	"time"

	"github.com/uber-go/gwr/source"
)

// NOTE: This approach is perhaps overfit to the json module's marshalling
// mindset.  A better interface (for performance) would work by passing a
// writer to the specific encoder, rather than a []byte-returning Marshal
// function.  This would be possible perhaps using something like
// io.MultiWriter.

// DataSource wraps a format-agnostic data source and provides one or
// more formats for it.
//
// DataSource implements:
// - DataSource to satisfy DataSources and low level protocols
// - ItemDataSource so that higher level protocols may add their own framing
// - GenericDataWatcher inwardly to the wrapped GenericDataSource
type DataSource struct {
	// TODO: better to have alternate implementations for each combination
	// rather than one with these nil checks
	source      source.GenericDataSource
	getSource   source.GetableDataSource
	watchSource source.WatchableDataSource
	watiSource  source.WatchInitableDataSource
	actiSource  source.ActivateWatchableDataSource

	formats     map[string]source.GenericDataFormat
	formatNames []string
	maxItems    int
	maxBatches  int
	maxWait     time.Duration

	procs     sync.WaitGroup
	watchLock sync.Mutex
	watchers  map[string]*marshaledWatcher
	active    bool
	itemChan  chan interface{}
	itemsChan chan []interface{}
}

func stringIt(item interface{}) ([]byte, error) {
	var s string
	if ss, ok := item.(fmt.Stringer); ok {
		s = ss.String()
	} else {
		s = fmt.Sprintf("%+v", item)
	}
	return []byte(s), nil
}

// NewDataSource creates a DataSource for a given format-agnostic data source
// and a map of marshalers
func NewDataSource(
	src source.GenericDataSource,
	formats map[string]source.GenericDataFormat,
) *DataSource {
	if formats == nil {
		formats = make(map[string]source.GenericDataFormat)
	}

	// source-defined formats
	if fmtsrc, ok := src.(source.GenericDataSourceFormats); ok {
		fmts := fmtsrc.Formats()
		for name, fmt := range fmts {
			formats[name] = fmt
		}
	}

	// standard json protocol
	if formats["json"] == nil {
		formats["json"] = LDJSONMarshal
	}

	// convenience templated text protocol
	if formats["text"] == nil {
		if txtsrc, ok := src.(source.TextTemplatedSource); ok {
			if tt := txtsrc.TextTemplate(); tt != nil {
				formats["text"] = NewTemplatedMarshal(tt)
			}
		}
	}

	// default to just string-ing it
	if formats["text"] == nil {
		formats["text"] = source.GenericDataFormatFunc(stringIt)
	}

	ds := &DataSource{
		source:   src,
		formats:  formats,
		watchers: make(map[string]*marshaledWatcher, len(formats)),
		// TODO: tunable
		maxItems:   100,
		maxBatches: 100,
		maxWait:    100 * time.Microsecond,
	}
	ds.getSource, _ = src.(source.GetableDataSource)
	ds.watchSource, _ = src.(source.WatchableDataSource)
	ds.watiSource, _ = src.(source.WatchInitableDataSource)
	ds.actiSource, _ = src.(source.ActivateWatchableDataSource)
	for name, format := range formats {
		ds.formatNames = append(ds.formatNames, name)
		ds.watchers[name] = newMarshaledWatcher(ds, format)
	}
	sort.Strings(ds.formatNames)

	if ds.watchSource != nil {
		ds.watchSource.SetWatcher(ds)
	}

	return ds
}

// Active returns true if there are any active watchers, false otherwise.  If
// Active returns false, so will any calls to HandleItem and HandleItems.
func (mds *DataSource) Active() bool {
	mds.watchLock.Lock()
	r := mds.active && mds.itemChan != nil && mds.itemsChan != nil
	mds.watchLock.Unlock()
	return r
}

// Name passes through the GenericDataSource.Name()
func (mds *DataSource) Name() string {
	return mds.source.Name()
}

// Formats returns the list of supported format names.
func (mds *DataSource) Formats() []string {
	return mds.formatNames
}

// Attrs returns arbitrary description information about the data source.
func (mds *DataSource) Attrs() map[string]interface{} {
	// TODO: support per-format Attrs?
	// TODO: any support for per-source Attrs?
	return nil
}

// Get marshals data source's Get data to the writer
func (mds *DataSource) Get(formatName string, w io.Writer) error {
	if mds.getSource == nil {
		return source.ErrNotGetable
	}
	format, ok := mds.formats[strings.ToLower(formatName)]
	if !ok {
		return source.ErrUnsupportedFormat
	}
	data := mds.getSource.Get()
	buf, err := format.MarshalGet(data)
	if err != nil {
		log.Printf("get marshaling error %v", err)
		return err
	}
	_, err = w.Write(buf)
	return err
}

// Watch marshals any data source GetInit data to the writer, and then
// retains a reference to the writer so that any future agnostic data source
// Watch(emit)'ed data gets marshaled to it as well
func (mds *DataSource) Watch(formatName string, w io.Writer) error {
	if mds.watchSource == nil {
		return source.ErrNotWatchable
	}

	mds.watchLock.Lock()
	acted := !mds.active
	err := func() error {
		defer mds.watchLock.Unlock()
		watcher, ok := mds.watchers[strings.ToLower(formatName)]
		if !ok {
			return source.ErrUnsupportedFormat
		}
		if err := watcher.init(w); err != nil {
			return err
		}
		if err := mds.startWatching(); err != nil {
			return err
		}
		return nil
	}()

	if err == nil && acted && mds.actiSource != nil {
		mds.actiSource.Activate()
	}
	return err
}

// WatchItems marshals any data source GetInit data as a single item to the
// ItemWatcher's HandleItem method.  The watcher is then retained and future
// items are marshaled to its HandleItem method.
func (mds *DataSource) WatchItems(formatName string, iw source.ItemWatcher) error {
	if mds.watchSource == nil {
		return source.ErrNotWatchable
	}

	mds.watchLock.Lock()
	acted := !mds.active
	err := func() error {
		defer mds.watchLock.Unlock()
		watcher, ok := mds.watchers[strings.ToLower(formatName)]
		if !ok {
			return source.ErrUnsupportedFormat
		}
		if err := watcher.initItems(iw); err != nil {
			return err
		}
		if err := mds.startWatching(); err != nil {
			return err
		}
		return nil
	}()

	if err == nil && acted && mds.actiSource != nil {
		mds.actiSource.Activate()
	}
	return err
}

// startWatching flips the active bit, creates new item channels, and starts a
// processing go routine; it assumes that the watchLock is being held by the
// caller.
func (mds *DataSource) startWatching() error {
	// TODO: we could optimize the only-one-format-being-watched case
	if mds.active {
		return nil
	}
	mds.active = true
	mds.itemChan = make(chan interface{}, mds.maxItems)
	mds.itemsChan = make(chan []interface{}, mds.maxBatches)
	mds.procs.Add(1)
	go mds.processItemChan(mds.itemChan, mds.itemsChan)
	return nil
}

// Drain closes the item channels, and waits for the item processor to finish.
// After drain, any remaining watchers are closed, and the source goes
// inactive.
func (mds *DataSource) Drain() {
	mds.watchLock.Lock()
	any := false
	if mds.itemChan != nil {
		close(mds.itemChan)
		any = true
		mds.itemChan = nil
	}
	if mds.itemsChan != nil {
		close(mds.itemsChan)
		any = true
		mds.itemsChan = nil
	}
	if any {
		mds.watchLock.Unlock()
		mds.procs.Wait()
		mds.watchLock.Lock()
	}
	stop := mds.active
	if stop {
		mds.active = false
	}
	mds.watchLock.Unlock()

	if stop {
		for _, watcher := range mds.watchers {
			watcher.Close()
		}
	}
}

func (mds *DataSource) processItemChan(itemChan chan interface{}, itemsChan chan []interface{}) {
	defer mds.procs.Done()

	stop := false

loop:
	for {
		mds.watchLock.Lock()
		active := mds.active
		watchers := mds.watchers
		mds.watchLock.Unlock()
		if !active {
			break loop
		}
		select {
		case item, ok := <-itemChan:
			if !ok {
				itemChan = nil
				continue loop
			}
			any := false
			for _, watcher := range watchers {
				if watcher.emit(item) {
					any = true
				}
			}
			if !any {
				stop = true
				break loop
			}

		case items, ok := <-itemsChan:
			if !ok {
				itemsChan = nil
				continue loop
			}
			any := false
			for _, watcher := range watchers {
				if watcher.emitBatch(items) {
					any = true
				}
			}
			if !any {
				stop = true
				break loop
			}

		default:
			if itemChan == nil && itemsChan == nil {
				break loop
			}
		}
	}

	mds.watchLock.Lock()
	if mds.itemChan == itemChan {
		mds.itemChan = nil
	}
	if mds.itemsChan == itemsChan {
		mds.itemsChan = nil
	}
	if stop {
		mds.active = false
	}
	mds.watchLock.Unlock()

	if stop {
		for _, watcher := range mds.watchers {
			watcher.Close()
		}
	}
}

// HandleItem implements GenericDataWatcher.HandleItem by passing the item to
// all current marshaledWatchers.
func (mds *DataSource) HandleItem(item interface{}) bool {
	if !mds.Active() {
		return false
	}
	select {
	case mds.itemChan <- item:
		return true
	case <-time.After(mds.maxWait):
		mds.watchLock.Lock()
		if !mds.active {
			mds.watchLock.Unlock()
			return false
		}
		mds.active = false
		mds.watchLock.Unlock()
		for _, watcher := range mds.watchers {
			watcher.Close()
		}
		return false
	}
}

// HandleItems implements GenericDataWatcher.HandleItems by passing the batch
// to all current marshaledWatchers.
func (mds *DataSource) HandleItems(items []interface{}) bool {
	if !mds.Active() {
		return false
	}
	select {
	case mds.itemsChan <- items:
		return true
	case <-time.After(mds.maxWait):
		mds.watchLock.Lock()
		if !mds.active {
			mds.watchLock.Unlock()
			return false
		}
		mds.active = false
		mds.watchLock.Unlock()
		for _, watcher := range mds.watchers {
			watcher.Close()
		}
		return false
	}
}
<|endoftext|>


<commit_before>// Copyright 2016 The Ebiten 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 restorable

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

	"github.com/hajimehoshi/ebiten/internal/affine"
	"github.com/hajimehoshi/ebiten/internal/graphics"
	"github.com/hajimehoshi/ebiten/internal/graphicscommand"
)

// drawImageHistoryItem is an item for history of draw-image commands.
type drawImageHistoryItem struct {
	image    *Image
	vertices []float32
	indices  []uint16
	colorm   *affine.ColorM
	mode     graphics.CompositeMode
	filter   graphics.Filter
}

// Image represents an image that can be restored when GL context is lost.
type Image struct {
	image *graphicscommand.Image

	basePixels []byte

	// drawImageHistory is a set of draw-image commands.
	// TODO: This should be merged with the similar command queue in package graphics (#433).
	drawImageHistory []*drawImageHistoryItem

	// stale indicates whether the image needs to be synced with GPU as soon as possible.
	stale bool

	// volatile indicates whether the image is cleared whenever a frame starts.
	volatile bool

	// screen indicates whether the image is used as an actual screen.
	screen bool

	w2 int
	h2 int
}

var dummyImage = newImageWithoutInit(16, 16, false)

// newImageWithoutInit creates an image without initialization.
//
// Note that Dispose is not called automatically.
func newImageWithoutInit(width, height int, volatile bool) *Image {
	i := &Image{
		image:    graphicscommand.NewImage(width, height),
		volatile: volatile,
	}
	theImages.add(i)
	return i
}

// NewImage creates an empty image with the given size.
//
// The returned image is cleared.
//
// Note that Dispose is not called automatically.
func NewImage(width, height int, volatile bool) *Image {
	i := newImageWithoutInit(width, height, volatile)
	return i
}

// NewScreenFramebufferImage creates a special image that framebuffer is one for the screen.
//
// The returned image is cleared.
//
// Note that Dispose is not called automatically.
func NewScreenFramebufferImage(width, height int) *Image {
	i := &Image{
		image:    graphicscommand.NewScreenFramebufferImage(width, height),
		volatile: false,
		screen:   true,
	}
	theImages.add(i)
	return i
}

func (i *Image) IsVolatile() bool {
	return i.volatile
}

// BasePixelsForTesting returns the image's basePixels for testing.
func (i *Image) BasePixelsForTesting() []byte {
	return i.basePixels
}

// Size returns the image's size.
func (i *Image) Size() (int, int) {
	return i.image.Size()
}

// SizePowerOf2 returns the next power of 2 values for the size.
func (i *Image) SizePowerOf2() (int, int) {
	if i.w2 == 0 || i.h2 == 0 {
		w, h := i.image.Size()
		i.w2 = graphics.NextPowerOf2Int(w)
		i.h2 = graphics.NextPowerOf2Int(h)
	}
	return i.w2, i.h2
}

// makeStale makes the image stale.
func (i *Image) makeStale() {
	i.basePixels = nil
	i.drawImageHistory = nil
	i.stale = true

	// Don't have to call makeStale recursively here.
	// Restoring is done after topological sorting is done.
	// If an image depends on another stale image, this means that
	// the former image can be restored from the latest state of the latter image.
}

// ReplacePixels replaces the image pixels with the given pixels slice.
//
// If pixels is nil, ReplacePixels clears the specified reagion.
func (i *Image) ReplacePixels(pixels []byte, x, y, width, height int) {
	w, h := i.image.Size()
	if width <= 0 || height <= 0 {
		panic("restorable: width/height must be positive")
	}
	if x < 0 || y < 0 || w <= x || h <= y || x+width <= 0 || y+height <= 0 || w < x+width || h < y+height {
		panic(fmt.Sprintf("restorable: out of range x: %d, y: %d, width: %d, height: %d", x, y, width, height))
	}

	// TODO: Avoid making other images stale if possible. (#514)
	// For this purpuse, images should remember which part of that is used for DrawImage.
	theImages.makeStaleIfDependingOn(i)

	if pixels != nil {
		i.image.ReplacePixels(pixels, x, y, width, height)
	} else {
		// There are not 'drawImageHistoryItem's for this image and dummyImage.
		// This means dummyImage might not be restored yet when this image is restored.
		// However, that's ok since this image will be stale or have updated pixel data
		// and this image can be restored without dummyImage.
		dw, dh := dummyImage.Size()
		w2 := graphics.NextPowerOf2Int(w)
		h2 := graphics.NextPowerOf2Int(h)
		vs := graphics.QuadVertices(w2, h2, 0, 0, dw, dh,
			float32(width)/float32(dw), 0, 0, float32(height)/float32(dh),
			float32(x), float32(y),
			1, 1, 1, 1)
		is := graphics.QuadIndices()
		i.image.DrawImage(dummyImage.image, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest)
	}

	if x == 0 && y == 0 && width == w && height == h {
		if pixels != nil {
			if i.basePixels == nil {
				i.basePixels = make([]byte, 4*w*h)
			}
			copy(i.basePixels, pixels)
		} else {
			// If basePixels is nil, the restored pixels are cleared.
			// See restore() implementation.
			i.basePixels = nil
		}
		i.drawImageHistory = nil
		i.stale = false
		return
	}
	if i.basePixels == nil {
		i.makeStale()
		return
	}
	if len(i.drawImageHistory) > 0 {
		i.makeStale()
		return
	}
	idx := 4 * (y*w + x)
	if pixels != nil {
		for j := 0; j < height; j++ {
			copy(i.basePixels[idx:idx+4*width], pixels[4*j*width:4*(j+1)*width])
			idx += 4 * w
		}
	} else {
		zeros := make([]byte, 4*width)
		for j := 0; j < height; j++ {
			copy(i.basePixels[idx:idx+4*width], zeros)
			idx += 4 * w
		}
	}
	i.stale = false
}

// DrawImage draws a given image img to the image.
func (i *Image) DrawImage(img *Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode graphics.CompositeMode, filter graphics.Filter) {
	if len(vertices) == 0 {
		return
	}
	theImages.makeStaleIfDependingOn(i)

	if img.stale || img.volatile || i.screen || !IsRestoringEnabled() {
		i.makeStale()
	} else {
		i.appendDrawImageHistory(img, vertices, indices, colorm, mode, filter)
	}
	i.image.DrawImage(img.image, vertices, indices, colorm, mode, filter)
}

// appendDrawImageHistory appends a draw-image history item to the image.
func (i *Image) appendDrawImageHistory(image *Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode graphics.CompositeMode, filter graphics.Filter) {
	if i.stale || i.volatile || i.screen {
		return
	}
	const maxDrawImageHistoryNum = 100
	if len(i.drawImageHistory)+1 > maxDrawImageHistoryNum {
		i.makeStale()
		return
	}
	// All images must be resolved and not stale each after frame.
	// So we don't have to care if image is stale or not here.
	item := &drawImageHistoryItem{
		image:    image,
		vertices: vertices,
		indices:  indices,
		colorm:   colorm,
		mode:     mode,
		filter:   filter,
	}
	i.drawImageHistory = append(i.drawImageHistory, item)
}

// At returns a color value at (x, y).
//
// Note that this must not be called until context is available.
func (i *Image) At(x, y int) color.RGBA {
	w, h := i.image.Size()
	if x < 0 || y < 0 || w <= x || h <= y {
		return color.RGBA{}
	}

	if i.basePixels == nil || i.drawImageHistory != nil || i.stale {
		graphicscommand.FlushCommands()
		i.readPixelsFromGPU()
		i.drawImageHistory = nil
		i.stale = false
	}

	// Even after readPixelsFromGPU, basePixels might be nil when OpenGL error happens.
	if i.basePixels == nil {
		return color.RGBA{}
	}

	idx := 4*x + 4*y*w
	r, g, b, a := i.basePixels[idx], i.basePixels[idx+1], i.basePixels[idx+2], i.basePixels[idx+3]
	return color.RGBA{r, g, b, a}
}

// makeStaleIfDependingOn makes the image stale if the image depends on target.
func (i *Image) makeStaleIfDependingOn(target *Image) {
	if i.stale {
		return
	}
	if i.dependsOn(target) {
		i.makeStale()
	}
}

// readPixelsFromGPU reads the pixels from GPU and resolves the image's 'stale' state.
func (i *Image) readPixelsFromGPU() {
	i.basePixels = i.image.Pixels()
	i.drawImageHistory = nil
	i.stale = false
}

// resolveStale resolves the image's 'stale' state.
func (i *Image) resolveStale() {
	if !IsRestoringEnabled() {
		return
	}

	if i.volatile {
		return
	}
	if i.screen {
		return
	}
	if !i.stale {
		return
	}
	i.readPixelsFromGPU()
}

// dependsOn returns a boolean value indicating whether the image depends on target.
func (i *Image) dependsOn(target *Image) bool {
	for _, c := range i.drawImageHistory {
		if c.image == target {
			return true
		}
	}
	return false
}

// dependingImages returns all images that is depended by the image.
func (i *Image) dependingImages() map[*Image]struct{} {
	r := map[*Image]struct{}{}
	for _, c := range i.drawImageHistory {
		r[c.image] = struct{}{}
	}
	return r
}

// hasDependency returns a boolean value indicating whether the image depends on another image.
func (i *Image) hasDependency() bool {
	if i.stale {
		return false
	}
	return len(i.drawImageHistory) > 0
}

// Restore restores *graphicscommand.Image from the pixels using its state.
func (i *Image) restore() error {
	w, h := i.image.Size()
	if i.screen {
		// The screen image should also be recreated because framebuffer might
		// be changed.
		i.image = graphicscommand.NewScreenFramebufferImage(w, h)
		i.basePixels = nil
		i.drawImageHistory = nil
		i.stale = false
		return nil
	}
	if i.volatile {
		i.image = graphicscommand.NewImage(w, h)
		i.basePixels = nil
		i.drawImageHistory = nil
		i.stale = false
		return nil
	}
	if i.stale {
		// TODO: panic here?
		return errors.New("restorable: pixels must not be stale when restoring")
	}
	gimg := graphicscommand.NewImage(w, h)
	if i.basePixels != nil {
		gimg.ReplacePixels(i.basePixels, 0, 0, w, h)
	} else {
		// Clear the image explicitly.
		pix := make([]uint8, w*h*4)
		gimg.ReplacePixels(pix, 0, 0, w, h)
	}
	for _, c := range i.drawImageHistory {
		// All dependencies must be already resolved.
		if c.image.hasDependency() {
			panic("not reached")
		}
		gimg.DrawImage(c.image.image, c.vertices, c.indices, c.colorm, c.mode, c.filter)
	}
	i.image = gimg

	i.basePixels = gimg.Pixels()
	i.drawImageHistory = nil
	i.stale = false
	return nil
}

// Dispose disposes the image.
//
// After disposing, calling the function of the image causes unexpected results.
func (i *Image) Dispose() {
	theImages.remove(i)

	i.image.Dispose()
	i.image = nil
	i.basePixels = nil
	i.drawImageHistory = nil
	i.stale = false
}

// IsInvalidated returns a boolean value indicating whether the image is invalidated.
//
// If an image is invalidated, GL context is lost and all the images should be restored asap.
func (i *Image) IsInvalidated() (bool, error) {
	// FlushCommands is required because c.offscreen.impl might not have an actual texture.
	graphicscommand.FlushCommands()
	if !IsRestoringEnabled() {
		return false, nil
	}

	return i.image.IsInvalidated(), nil
}
<commit_msg>restorable: Refactoring: Remove newImageWithoutInit<commit_after>// Copyright 2016 The Ebiten 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 restorable

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

	"github.com/hajimehoshi/ebiten/internal/affine"
	"github.com/hajimehoshi/ebiten/internal/graphics"
	"github.com/hajimehoshi/ebiten/internal/graphicscommand"
)

// drawImageHistoryItem is an item for history of draw-image commands.
type drawImageHistoryItem struct {
	image    *Image
	vertices []float32
	indices  []uint16
	colorm   *affine.ColorM
	mode     graphics.CompositeMode
	filter   graphics.Filter
}

// Image represents an image that can be restored when GL context is lost.
type Image struct {
	image *graphicscommand.Image

	basePixels []byte

	// drawImageHistory is a set of draw-image commands.
	// TODO: This should be merged with the similar command queue in package graphics (#433).
	drawImageHistory []*drawImageHistoryItem

	// stale indicates whether the image needs to be synced with GPU as soon as possible.
	stale bool

	// volatile indicates whether the image is cleared whenever a frame starts.
	volatile bool

	// screen indicates whether the image is used as an actual screen.
	screen bool

	w2 int
	h2 int
}

var dummyImage = NewImage(16, 16, false)

// NewImage creates an empty image with the given size.
//
// The returned image is cleared.
//
// Note that Dispose is not called automatically.
func NewImage(width, height int, volatile bool) *Image {
	i := &Image{
		image:    graphicscommand.NewImage(width, height),
		volatile: volatile,
	}
	theImages.add(i)
	return i
}

// NewScreenFramebufferImage creates a special image that framebuffer is one for the screen.
//
// The returned image is cleared.
//
// Note that Dispose is not called automatically.
func NewScreenFramebufferImage(width, height int) *Image {
	i := &Image{
		image:    graphicscommand.NewScreenFramebufferImage(width, height),
		volatile: false,
		screen:   true,
	}
	theImages.add(i)
	return i
}

func (i *Image) IsVolatile() bool {
	return i.volatile
}

// BasePixelsForTesting returns the image's basePixels for testing.
func (i *Image) BasePixelsForTesting() []byte {
	return i.basePixels
}

// Size returns the image's size.
func (i *Image) Size() (int, int) {
	return i.image.Size()
}

// SizePowerOf2 returns the next power of 2 values for the size.
func (i *Image) SizePowerOf2() (int, int) {
	if i.w2 == 0 || i.h2 == 0 {
		w, h := i.image.Size()
		i.w2 = graphics.NextPowerOf2Int(w)
		i.h2 = graphics.NextPowerOf2Int(h)
	}
	return i.w2, i.h2
}

// makeStale makes the image stale.
func (i *Image) makeStale() {
	i.basePixels = nil
	i.drawImageHistory = nil
	i.stale = true

	// Don't have to call makeStale recursively here.
	// Restoring is done after topological sorting is done.
	// If an image depends on another stale image, this means that
	// the former image can be restored from the latest state of the latter image.
}

// ReplacePixels replaces the image pixels with the given pixels slice.
//
// If pixels is nil, ReplacePixels clears the specified reagion.
func (i *Image) ReplacePixels(pixels []byte, x, y, width, height int) {
	w, h := i.image.Size()
	if width <= 0 || height <= 0 {
		panic("restorable: width/height must be positive")
	}
	if x < 0 || y < 0 || w <= x || h <= y || x+width <= 0 || y+height <= 0 || w < x+width || h < y+height {
		panic(fmt.Sprintf("restorable: out of range x: %d, y: %d, width: %d, height: %d", x, y, width, height))
	}

	// TODO: Avoid making other images stale if possible. (#514)
	// For this purpuse, images should remember which part of that is used for DrawImage.
	theImages.makeStaleIfDependingOn(i)

	if pixels != nil {
		i.image.ReplacePixels(pixels, x, y, width, height)
	} else {
		// There are not 'drawImageHistoryItem's for this image and dummyImage.
		// This means dummyImage might not be restored yet when this image is restored.
		// However, that's ok since this image will be stale or have updated pixel data
		// and this image can be restored without dummyImage.
		dw, dh := dummyImage.Size()
		w2 := graphics.NextPowerOf2Int(w)
		h2 := graphics.NextPowerOf2Int(h)
		vs := graphics.QuadVertices(w2, h2, 0, 0, dw, dh,
			float32(width)/float32(dw), 0, 0, float32(height)/float32(dh),
			float32(x), float32(y),
			1, 1, 1, 1)
		is := graphics.QuadIndices()
		i.image.DrawImage(dummyImage.image, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest)
	}

	if x == 0 && y == 0 && width == w && height == h {
		if pixels != nil {
			if i.basePixels == nil {
				i.basePixels = make([]byte, 4*w*h)
			}
			copy(i.basePixels, pixels)
		} else {
			// If basePixels is nil, the restored pixels are cleared.
			// See restore() implementation.
			i.basePixels = nil
		}
		i.drawImageHistory = nil
		i.stale = false
		return
	}
	if i.basePixels == nil {
		i.makeStale()
		return
	}
	if len(i.drawImageHistory) > 0 {
		i.makeStale()
		return
	}
	idx := 4 * (y*w + x)
	if pixels != nil {
		for j := 0; j < height; j++ {
			copy(i.basePixels[idx:idx+4*width], pixels[4*j*width:4*(j+1)*width])
			idx += 4 * w
		}
	} else {
		zeros := make([]byte, 4*width)
		for j := 0; j < height; j++ {
			copy(i.basePixels[idx:idx+4*width], zeros)
			idx += 4 * w
		}
	}
	i.stale = false
}

// DrawImage draws a given image img to the image.
func (i *Image) DrawImage(img *Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode graphics.CompositeMode, filter graphics.Filter) {
	if len(vertices) == 0 {
		return
	}
	theImages.makeStaleIfDependingOn(i)

	if img.stale || img.volatile || i.screen || !IsRestoringEnabled() {
		i.makeStale()
	} else {
		i.appendDrawImageHistory(img, vertices, indices, colorm, mode, filter)
	}
	i.image.DrawImage(img.image, vertices, indices, colorm, mode, filter)
}

// appendDrawImageHistory appends a draw-image history item to the image.
func (i *Image) appendDrawImageHistory(image *Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode graphics.CompositeMode, filter graphics.Filter) {
	if i.stale || i.volatile || i.screen {
		return
	}
	const maxDrawImageHistoryNum = 100
	if len(i.drawImageHistory)+1 > maxDrawImageHistoryNum {
		i.makeStale()
		return
	}
	// All images must be resolved and not stale each after frame.
	// So we don't have to care if image is stale or not here.
	item := &drawImageHistoryItem{
		image:    image,
		vertices: vertices,
		indices:  indices,
		colorm:   colorm,
		mode:     mode,
		filter:   filter,
	}
	i.drawImageHistory = append(i.drawImageHistory, item)
}

// At returns a color value at (x, y).
//
// Note that this must not be called until context is available.
func (i *Image) At(x, y int) color.RGBA {
	w, h := i.image.Size()
	if x < 0 || y < 0 || w <= x || h <= y {
		return color.RGBA{}
	}

	if i.basePixels == nil || i.drawImageHistory != nil || i.stale {
		graphicscommand.FlushCommands()
		i.readPixelsFromGPU()
		i.drawImageHistory = nil
		i.stale = false
	}

	// Even after readPixelsFromGPU, basePixels might be nil when OpenGL error happens.
	if i.basePixels == nil {
		return color.RGBA{}
	}

	idx := 4*x + 4*y*w
	r, g, b, a := i.basePixels[idx], i.basePixels[idx+1], i.basePixels[idx+2], i.basePixels[idx+3]
	return color.RGBA{r, g, b, a}
}

// makeStaleIfDependingOn makes the image stale if the image depends on target.
func (i *Image) makeStaleIfDependingOn(target *Image) {
	if i.stale {
		return
	}
	if i.dependsOn(target) {
		i.makeStale()
	}
}

// readPixelsFromGPU reads the pixels from GPU and resolves the image's 'stale' state.
func (i *Image) readPixelsFromGPU() {
	i.basePixels = i.image.Pixels()
	i.drawImageHistory = nil
	i.stale = false
}

// resolveStale resolves the image's 'stale' state.
func (i *Image) resolveStale() {
	if !IsRestoringEnabled() {
		return
	}

	if i.volatile {
		return
	}
	if i.screen {
		return
	}
	if !i.stale {
		return
	}
	i.readPixelsFromGPU()
}

// dependsOn returns a boolean value indicating whether the image depends on target.
func (i *Image) dependsOn(target *Image) bool {
	for _, c := range i.drawImageHistory {
		if c.image == target {
			return true
		}
	}
	return false
}

// dependingImages returns all images that is depended by the image.
func (i *Image) dependingImages() map[*Image]struct{} {
	r := map[*Image]struct{}{}
	for _, c := range i.drawImageHistory {
		r[c.image] = struct{}{}
	}
	return r
}

// hasDependency returns a boolean value indicating whether the image depends on another image.
func (i *Image) hasDependency() bool {
	if i.stale {
		return false
	}
	return len(i.drawImageHistory) > 0
}

// Restore restores *graphicscommand.Image from the pixels using its state.
func (i *Image) restore() error {
	w, h := i.image.Size()
	if i.screen {
		// The screen image should also be recreated because framebuffer might
		// be changed.
		i.image = graphicscommand.NewScreenFramebufferImage(w, h)
		i.basePixels = nil
		i.drawImageHistory = nil
		i.stale = false
		return nil
	}
	if i.volatile {
		i.image = graphicscommand.NewImage(w, h)
		i.basePixels = nil
		i.drawImageHistory = nil
		i.stale = false
		return nil
	}
	if i.stale {
		// TODO: panic here?
		return errors.New("restorable: pixels must not be stale when restoring")
	}
	gimg := graphicscommand.NewImage(w, h)
	if i.basePixels != nil {
		gimg.ReplacePixels(i.basePixels, 0, 0, w, h)
	} else {
		// Clear the image explicitly.
		pix := make([]uint8, w*h*4)
		gimg.ReplacePixels(pix, 0, 0, w, h)
	}
	for _, c := range i.drawImageHistory {
		// All dependencies must be already resolved.
		if c.image.hasDependency() {
			panic("not reached")
		}
		gimg.DrawImage(c.image.image, c.vertices, c.indices, c.colorm, c.mode, c.filter)
	}
	i.image = gimg

	i.basePixels = gimg.Pixels()
	i.drawImageHistory = nil
	i.stale = false
	return nil
}

// Dispose disposes the image.
//
// After disposing, calling the function of the image causes unexpected results.
func (i *Image) Dispose() {
	theImages.remove(i)

	i.image.Dispose()
	i.image = nil
	i.basePixels = nil
	i.drawImageHistory = nil
	i.stale = false
}

// IsInvalidated returns a boolean value indicating whether the image is invalidated.
//
// If an image is invalidated, GL context is lost and all the images should be restored asap.
func (i *Image) IsInvalidated() (bool, error) {
	// FlushCommands is required because c.offscreen.impl might not have an actual texture.
	graphicscommand.FlushCommands()
	if !IsRestoringEnabled() {
		return false, nil
	}

	return i.image.IsInvalidated(), nil
}
<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
// Author: jacobsa@google.com (Aaron Jacobs)

package gcsproxy

import (
	"fmt"
	"io"
	"log"
	"math"
	"os"
	"strings"

	"github.com/jacobsa/gcloud/gcs"
	"github.com/jacobsa/gcloud/syncutil"
	"golang.org/x/net/context"
	"google.golang.org/cloud/storage"
)

// A view on an object in GCS that allows random access reads and writes.
//
// Reads may involve reading from a local cache. Writes are buffered locally
// until the Sync method is called, at which time a new generation of the
// object is created.
//
// All methods are safe for concurrent access. Concurrent readers and writers
// within process receive the same guarantees as with POSIX files.
type ProxyObject struct {
	/////////////////////////
	// Dependencies
	/////////////////////////

	logger *log.Logger
	bucket gcs.Bucket

	/////////////////////////
	// Constant data
	/////////////////////////

	// The name of the GCS object for which we are a proxy. Might not exist in
	// the bucket.
	name string

	/////////////////////////
	// Mutable state
	/////////////////////////

	mu syncutil.InvariantMutex

	// The specific generation of the object from which our local state is
	// branched. If we have no local state, the contents of this object are
	// exactly our contents. May be nil if NoteLatest was never called.
	//
	// INVARIANT: If source != nil, source.Size >= 0
	// INVARIANT: If source != nil, source.Name == name
	source *storage.Object // GUARDED_BY(mu)

	// A local temporary file containing the contents of our source (or the empty
	// string if no source) along with any local modifications. The authority on
	// our view of the object when non-nil.
	//
	// A nil file is to be regarded as empty, but is not authoritative unless
	// source is also nil.
	localFile *os.File // GUARDED_BY(mu)

	// false if the contents of localFile may be different from the contents of
	// the object referred to by source. Sync needs to do work iff this is true.
	//
	// INVARIANT: If false, then source != nil.
	dirty bool // GUARDED_BY(mu)
}

var _ io.ReaderAt = &ProxyObject{}
var _ io.WriterAt = &ProxyObject{}

// Create a new view on the GCS object with the given name. The remote object
// is assumed to be non-existent, so that the local contents are empty. Use
// NoteLatest to change that if necessary.
func NewProxyObject(
	bucket gcs.Bucket,
	name string) (po *ProxyObject, err error) {
	po = &ProxyObject{
		logger: getLogger(),
		bucket: bucket,
		name:   name,

		// Initial state: empty contents, dirty. (The remote object needs to be
		// truncated.)
		source:    nil,
		localFile: nil,
		dirty:     true,
	}

	po.mu = syncutil.NewInvariantMutex(po.checkInvariants)
	return
}

// SHARED_LOCKS_REQUIRED(po.mu)
func (po *ProxyObject) checkInvariants() {
	if po.source != nil && po.source.Size <= 0 {
		if po.source.Size <= 0 {
			panic(fmt.Sprintf("Non-sensical source size: %v", po.source.Size))
		}

		if po.source.Name != po.name {
			panic(fmt.Sprintf("Name mismatch: %s vs. %s", po.source.Name, po.name))
		}
	}

	if !po.dirty && po.source == nil {
		panic("A clean proxy must have a source set.")
	}
}

// Inform the proxy object of the most recently observed generation of the
// object of interest in GCS.
//
// If this is no newer than the newest generation that has previously been
// observed, it is ignored. Otherwise, it becomes the definitive source of data
// for the object. Any local-only state is clobbered, including local
// modifications.
func (po *ProxyObject) NoteLatest(o storage.Object) (err error) {
	// Sanity check the input.
	if o.Size < 0 {
		err = fmt.Errorf("Object contains negative size: %v", o.Size)
		return
	}

	if o.Name != po.name {
		err = fmt.Errorf("Object name mismatch: %s vs. %s", o.Name, po.name)
		return
	}

	// Do nothing if nothing has changed.
	if po.source != nil && po.source.Generation == o.Generation {
		return
	}

	// Throw out the local file, if any.
	if po.localFile != nil {
		path := po.localFile.Name()

		if err = po.localFile.Close(); err != nil {
			err = fmt.Errorf("Closing local file: %v", err)
			return
		}

		if err = os.Remove(path); err != nil {
			err = fmt.Errorf("Unlinking local file: %v", err)
			return
		}
	}

	// Reset state.
	po.source = &o
	po.localFile = nil
	po.dirty = false

	return
}

// Return the current size in bytes of our view of the content.
func (po *ProxyObject) Size() (n uint64, err error) {
	// If we have a local file, it is authoritative.
	if po.localFile != nil {
		var fi os.FileInfo
		if fi, err = po.localFile.Stat(); err != nil {
			err = fmt.Errorf("localFile.Stat: %v", err)
			return
		}

		nSigned := fi.Size()
		if nSigned < 0 {
			err = fmt.Errorf("Stat returned nonsense size: %v", nSigned)
			return
		}

		n = uint64(nSigned)
		return
	}

	// Otherwise, if we have a source then it is authoritative.
	if po.source != nil {
		n = uint64(po.source.Size)
		return
	}

	// Otherwise, we are empty.
	return
}

// Make a random access read into our view of the content. May block for
// network access.
func (po *ProxyObject) ReadAt(buf []byte, offset int64) (n int, err error) {
	if err = po.ensureLocalFile(); err != nil {
		return
	}

	n, err = po.localFile.ReadAt(buf, offset)
	return
}

// Make a random access write into our view of the content. May block for
// network access. Not guaranteed to be reflected remotely until after Sync is
// called successfully.
func (po *ProxyObject) WriteAt(buf []byte, offset int64) (n int, err error) {
	if err = po.ensureLocalFile(); err != nil {
		return
	}

	po.dirty = true
	n, err = po.localFile.WriteAt(buf, offset)
	return
}

// Truncate our view of the content to the given number of bytes, extending if
// n is greater than Size(). May block for network access. Not guaranteed to be
// reflected remotely until after Sync is called successfully.
func (po *ProxyObject) Truncate(n uint64) (err error) {
	if err = po.ensureLocalFile(); err != nil {
		return
	}

	// Convert to signed, which is what os.File wants.
	if n > math.MaxInt64 {
		err = fmt.Errorf("Illegal offset: %v", n)
		return
	}

	po.dirty = true
	err = po.localFile.Truncate(int64(n))
	return
}

// Ensure that the remote object reflects the local state, returning a record
// for a generation that does. Clobbers the remote version. Does no work if the
// remote version is already up to date.
func (po *ProxyObject) Sync(ctx context.Context) (o storage.Object, err error) {
	// Is there anything to do?
	if !po.dirty {
		o = *po.source
		return
	}

	// Choose a reader.
	var contents io.Reader
	if po.localFile != nil {
		contents = po.localFile
	} else {
		contents = strings.NewReader("")
	}

	// Create a new generation of the object.
	req := &gcs.CreateObjectRequest{
		Attrs: storage.ObjectAttrs{
			Name: po.name,
		},
		Contents: contents,
	}

	created, err := po.bucket.CreateObject(ctx, req)
	if err != nil {
		return
	}

	o = *created

	// Update local state.
	po.source = created
	po.dirty = false

	return
}

// Ensure that po.localFile != nil and contains the correct contents.
func (po *ProxyObject) ensureLocalFile() (err error)
<commit_msg>Renamed ProxyObject -> ObjectProxy.<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
// Author: jacobsa@google.com (Aaron Jacobs)

package gcsproxy

import (
	"fmt"
	"io"
	"log"
	"math"
	"os"
	"strings"

	"github.com/jacobsa/gcloud/gcs"
	"github.com/jacobsa/gcloud/syncutil"
	"golang.org/x/net/context"
	"google.golang.org/cloud/storage"
)

// A view on an object in GCS that allows random access reads and writes.
//
// Reads may involve reading from a local cache. Writes are buffered locally
// until the Sync method is called, at which time a new generation of the
// object is created.
//
// All methods are safe for concurrent access. Concurrent readers and writers
// within process receive the same guarantees as with POSIX files.
type ObjectProxy struct {
	/////////////////////////
	// Dependencies
	/////////////////////////

	logger *log.Logger
	bucket gcs.Bucket

	/////////////////////////
	// Constant data
	/////////////////////////

	// The name of the GCS object for which we are a proxy. Might not exist in
	// the bucket.
	name string

	/////////////////////////
	// Mutable state
	/////////////////////////

	mu syncutil.InvariantMutex

	// The specific generation of the object from which our local state is
	// branched. If we have no local state, the contents of this object are
	// exactly our contents. May be nil if NoteLatest was never called.
	//
	// INVARIANT: If source != nil, source.Size >= 0
	// INVARIANT: If source != nil, source.Name == name
	source *storage.Object // GUARDED_BY(mu)

	// A local temporary file containing the contents of our source (or the empty
	// string if no source) along with any local modifications. The authority on
	// our view of the object when non-nil.
	//
	// A nil file is to be regarded as empty, but is not authoritative unless
	// source is also nil.
	localFile *os.File // GUARDED_BY(mu)

	// false if the contents of localFile may be different from the contents of
	// the object referred to by source. Sync needs to do work iff this is true.
	//
	// INVARIANT: If false, then source != nil.
	dirty bool // GUARDED_BY(mu)
}

var _ io.ReaderAt = &ObjectProxy{}
var _ io.WriterAt = &ObjectProxy{}

// Create a new view on the GCS object with the given name. The remote object
// is assumed to be non-existent, so that the local contents are empty. Use
// NoteLatest to change that if necessary.
func NewObjectProxy(
	bucket gcs.Bucket,
	name string) (op *ObjectProxy, err error) {
	op = &ObjectProxy{
		logger: getLogger(),
		bucket: bucket,
		name:   name,

		// Initial state: empty contents, dirty. (The remote object needs to be
		// truncated.)
		source:    nil,
		localFile: nil,
		dirty:     true,
	}

	op.mu = syncutil.NewInvariantMutex(op.checkInvariants)
	return
}

// SHARED_LOCKS_REQUIRED(op.mu)
func (op *ObjectProxy) checkInvariants() {
	if op.source != nil && op.source.Size <= 0 {
		if op.source.Size <= 0 {
			panic(fmt.Sprintf("Non-sensical source size: %v", op.source.Size))
		}

		if op.source.Name != op.name {
			panic(fmt.Sprintf("Name mismatch: %s vs. %s", op.source.Name, op.name))
		}
	}

	if !op.dirty && op.source == nil {
		panic("A clean proxy must have a source set.")
	}
}

// Inform the proxy object of the most recently observed generation of the
// object of interest in GCS.
//
// If this is no newer than the newest generation that has previously been
// observed, it is ignored. Otherwise, it becomes the definitive source of data
// for the object. Any local-only state is clobbered, including local
// modifications.
func (op *ObjectProxy) NoteLatest(o storage.Object) (err error) {
	// Sanity check the input.
	if o.Size < 0 {
		err = fmt.Errorf("Object contains negative size: %v", o.Size)
		return
	}

	if o.Name != op.name {
		err = fmt.Errorf("Object name mismatch: %s vs. %s", o.Name, op.name)
		return
	}

	// Do nothing if nothing has changed.
	if op.source != nil && op.source.Generation == o.Generation {
		return
	}

	// Throw out the local file, if any.
	if op.localFile != nil {
		path := op.localFile.Name()

		if err = op.localFile.Close(); err != nil {
			err = fmt.Errorf("Closing local file: %v", err)
			return
		}

		if err = os.Remove(path); err != nil {
			err = fmt.Errorf("Unlinking local file: %v", err)
			return
		}
	}

	// Reset state.
	op.source = &o
	op.localFile = nil
	op.dirty = false

	return
}

// Return the current size in bytes of our view of the content.
func (op *ObjectProxy) Size() (n uint64, err error) {
	// If we have a local file, it is authoritative.
	if op.localFile != nil {
		var fi os.FileInfo
		if fi, err = op.localFile.Stat(); err != nil {
			err = fmt.Errorf("localFile.Stat: %v", err)
			return
		}

		nSigned := fi.Size()
		if nSigned < 0 {
			err = fmt.Errorf("Stat returned nonsense size: %v", nSigned)
			return
		}

		n = uint64(nSigned)
		return
	}

	// Otherwise, if we have a source then it is authoritative.
	if op.source != nil {
		n = uint64(op.source.Size)
		return
	}

	// Otherwise, we are empty.
	return
}

// Make a random access read into our view of the content. May block for
// network access.
func (op *ObjectProxy) ReadAt(buf []byte, offset int64) (n int, err error) {
	if err = op.ensureLocalFile(); err != nil {
		return
	}

	n, err = op.localFile.ReadAt(buf, offset)
	return
}

// Make a random access write into our view of the content. May block for
// network access. Not guaranteed to be reflected remotely until after Sync is
// called successfully.
func (op *ObjectProxy) WriteAt(buf []byte, offset int64) (n int, err error) {
	if err = op.ensureLocalFile(); err != nil {
		return
	}

	op.dirty = true
	n, err = op.localFile.WriteAt(buf, offset)
	return
}

// Truncate our view of the content to the given number of bytes, extending if
// n is greater than Size(). May block for network access. Not guaranteed to be
// reflected remotely until after Sync is called successfully.
func (op *ObjectProxy) Truncate(n uint64) (err error) {
	if err = op.ensureLocalFile(); err != nil {
		return
	}

	// Convert to signed, which is what os.File wants.
	if n > math.MaxInt64 {
		err = fmt.Errorf("Illegal offset: %v", n)
		return
	}

	op.dirty = true
	err = op.localFile.Truncate(int64(n))
	return
}

// Ensure that the remote object reflects the local state, returning a record
// for a generation that does. Clobbers the remote version. Does no work if the
// remote version is already up to date.
func (op *ObjectProxy) Sync(ctx context.Context) (o storage.Object, err error) {
	// Is there anything to do?
	if !op.dirty {
		o = *op.source
		return
	}

	// Choose a reader.
	var contents io.Reader
	if op.localFile != nil {
		contents = op.localFile
	} else {
		contents = strings.NewReader("")
	}

	// Create a new generation of the object.
	req := &gcs.CreateObjectRequest{
		Attrs: storage.ObjectAttrs{
			Name: op.name,
		},
		Contents: contents,
	}

	created, err := op.bucket.CreateObject(ctx, req)
	if err != nil {
		return
	}

	o = *created

	// Update local state.
	op.source = created
	op.dirty = false

	return
}

// Ensure that op.localFile != nil and contains the correct contents.
func (op *ObjectProxy) ensureLocalFile() (err error)
<|endoftext|>


<commit_before>package kubernetes

import (
	"testing"
	"time"

	crdv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
	crdfake "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/discovery"
	toolscache "k8s.io/client-go/tools/cache"
)

type chainHandler struct {
	first toolscache.ResourceEventHandler
	next  toolscache.ResourceEventHandler
}

func (h chainHandler) OnAdd(obj interface{}) {
	h.first.OnAdd(obj)
	h.next.OnAdd(obj)
}

func (h chainHandler) OnUpdate(old, new interface{}) {
	h.first.OnUpdate(old, new)
	h.next.OnUpdate(old, new)
}

func (h chainHandler) OnDelete(old interface{}) {
	h.first.OnDelete(old)
	h.next.OnDelete(old)
}

func TestCachedDiscovery(t *testing.T) {
	coreClient := makeFakeClient()

	myCRD := &crdv1beta1.CustomResourceDefinition{
		ObjectMeta: metav1.ObjectMeta{
			Name: "custom",
		},
	}
	crdClient := crdfake.NewSimpleClientset(myCRD)

	// Here's my fake API resource
	myAPI := &metav1.APIResourceList{
		GroupVersion: "foo/v1",
		APIResources: []metav1.APIResource{
			{Name: "customs", SingularName: "custom", Namespaced: true, Kind: "Custom", Verbs: getAndList},
		},
	}

	apiResources := coreClient.Fake.Resources
	coreClient.Fake.Resources = append(apiResources, myAPI)

	shutdown := make(chan struct{})
	defer close(shutdown)

	// this extra handler means we can synchronise on the add later
	// being processed
	allowAdd := make(chan interface{})

	addHandler := toolscache.ResourceEventHandlerFuncs{
		AddFunc: func(obj interface{}) {
			allowAdd <- obj
		},
	}
	makeHandler := func(d discovery.CachedDiscoveryInterface) toolscache.ResourceEventHandler {
		return chainHandler{first: addHandler, next: makeInvalidatingHandler(d)}
	}

	cachedDisco, store, _ := makeCachedDiscovery(coreClient.Discovery(), crdClient, shutdown, makeHandler)

	saved := getDefaultNamespace
	getDefaultNamespace = func() (string, error) { return "bar-ns", nil }
	defer func() { getDefaultNamespace = saved }()
	namespacer, err := NewNamespacer(cachedDisco)
	if err != nil {
		t.Fatal(err)
	}

	namespaced, err := namespacer.lookupNamespaced("foo/v1", "Custom")
	if err != nil {
		t.Fatal(err)
	}
	if !namespaced {
		t.Error("got false from lookupNamespaced, expecting true")
	}

	// In a cluster, we'd rely on the apiextensions server to reflect
	// changes to CRDs to changes in the API resources. Here I will be
	// more narrow, and just test that the API resources are reloaded
	// when a CRD is updated or deleted.

	// This is delicate: we can't just change the value in-place,
	// since that will update everyone's record of it, and the test
	// below will trivially succeed.
	updatedAPI := &metav1.APIResourceList{
		GroupVersion: "foo/v1",
		APIResources: []metav1.APIResource{
			{Name: "customs", SingularName: "custom", Namespaced: false /* <-- changed */, Kind: "Custom", Verbs: getAndList},
		},
	}
	coreClient.Fake.Resources = append(apiResources, updatedAPI)

	// Provoke the cached discovery client into invalidating
	_, err = crdClient.ApiextensionsV1beta1().CustomResourceDefinitions().Update(myCRD)
	if err != nil {
		t.Fatal(err)
	}

	// Wait for the update to "go through"
	select {
	case <-allowAdd:
		break
	case <-time.After(time.Second):
		t.Fatal("timed out waiting for Add to happen")
	}

	_, exists, err := store.Get(myCRD)
	if err != nil {
		t.Error(err)
	}
	if !exists {
		t.Error("does not exist")
	}

	namespaced, err = namespacer.lookupNamespaced("foo/v1", "Custom")
	if err != nil {
		t.Fatal(err)
	}
	if namespaced {
		t.Error("got true from lookupNamespaced, expecting false (after changing it)")
	}
}
<commit_msg>Fix flaky cached discovery test<commit_after>package kubernetes

import (
	"testing"
	"time"

	crdv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
	crdfake "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/discovery"
	toolscache "k8s.io/client-go/tools/cache"
)

type chainHandler struct {
	first toolscache.ResourceEventHandler
	next  toolscache.ResourceEventHandler
}

func (h chainHandler) OnAdd(obj interface{}) {
	h.first.OnAdd(obj)
	h.next.OnAdd(obj)
}

func (h chainHandler) OnUpdate(old, new interface{}) {
	h.first.OnUpdate(old, new)
	h.next.OnUpdate(old, new)
}

func (h chainHandler) OnDelete(old interface{}) {
	h.first.OnDelete(old)
	h.next.OnDelete(old)
}

func TestCachedDiscovery(t *testing.T) {
	coreClient := makeFakeClient()

	myCRD := &crdv1beta1.CustomResourceDefinition{
		ObjectMeta: metav1.ObjectMeta{
			Name: "custom",
		},
	}
	crdClient := crdfake.NewSimpleClientset(myCRD)

	// Here's my fake API resource
	myAPI := &metav1.APIResourceList{
		GroupVersion: "foo/v1",
		APIResources: []metav1.APIResource{
			{Name: "customs", SingularName: "custom", Namespaced: true, Kind: "Custom", Verbs: getAndList},
		},
	}

	apiResources := coreClient.Fake.Resources
	coreClient.Fake.Resources = append(apiResources, myAPI)

	shutdown := make(chan struct{})
	defer close(shutdown)

	// this extra handler means we can synchronise on the add later
	// being processed
	allowAdd := make(chan interface{})

	addHandler := toolscache.ResourceEventHandlerFuncs{
		AddFunc: func(obj interface{}) {
			allowAdd <- obj
		},
	}
	makeHandler := func(d discovery.CachedDiscoveryInterface) toolscache.ResourceEventHandler {
		return chainHandler{first: makeInvalidatingHandler(d), next: addHandler}
	}

	cachedDisco, store, _ := makeCachedDiscovery(coreClient.Discovery(), crdClient, shutdown, makeHandler)

	saved := getDefaultNamespace
	getDefaultNamespace = func() (string, error) { return "bar-ns", nil }
	defer func() { getDefaultNamespace = saved }()
	namespacer, err := NewNamespacer(cachedDisco)
	if err != nil {
		t.Fatal(err)
	}

	namespaced, err := namespacer.lookupNamespaced("foo/v1", "Custom")
	if err != nil {
		t.Fatal(err)
	}
	if !namespaced {
		t.Error("got false from lookupNamespaced, expecting true")
	}

	// In a cluster, we'd rely on the apiextensions server to reflect
	// changes to CRDs to changes in the API resources. Here I will be
	// more narrow, and just test that the API resources are reloaded
	// when a CRD is updated or deleted.

	// This is delicate: we can't just change the value in-place,
	// since that will update everyone's record of it, and the test
	// below will trivially succeed.
	updatedAPI := &metav1.APIResourceList{
		GroupVersion: "foo/v1",
		APIResources: []metav1.APIResource{
			{Name: "customs", SingularName: "custom", Namespaced: false /* <-- changed */, Kind: "Custom", Verbs: getAndList},
		},
	}
	coreClient.Fake.Resources = append(apiResources, updatedAPI)

	// Provoke the cached discovery client into invalidating
	_, err = crdClient.ApiextensionsV1beta1().CustomResourceDefinitions().Update(myCRD)
	if err != nil {
		t.Fatal(err)
	}

	// Wait for the update to "go through"
	select {
	case <-allowAdd:
		break
	case <-time.After(time.Second):
		t.Fatal("timed out waiting for Add to happen")
	}

	_, exists, err := store.Get(myCRD)
	if err != nil {
		t.Error(err)
	}
	if !exists {
		t.Error("does not exist")
	}

	namespaced, err = namespacer.lookupNamespaced("foo/v1", "Custom")
	if err != nil {
		t.Fatal(err)
	}
	if namespaced {
		t.Error("got true from lookupNamespaced, expecting false (after changing it)")
	}
}
<|endoftext|>