<commit_before>/*
Copyright IBM Corp All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package e2e

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"syscall"

	docker "github.com/fsouza/go-dockerclient"
	"github.com/hyperledger/fabric-lib-go/healthz"
	"github.com/hyperledger/fabric/integration/nwo"
	"github.com/hyperledger/fabric/integration/runner"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/tedsuo/ifrit"
	"github.com/tedsuo/ifrit/ginkgomon"
)

var _ = Describe("Health", func() {
	var (
		testDir string
		client  *docker.Client
		network *nwo.Network
		process ifrit.Process
	)

	BeforeEach(func() {
		var err error
		testDir, err = ioutil.TempDir("", "e2e")
		Expect(err).NotTo(HaveOccurred())

		client, err = docker.NewClientFromEnv()
		Expect(err).NotTo(HaveOccurred())

		config := nwo.BasicKafka()
		config.Consensus.Brokers = 3

		network = nwo.New(config, testDir, client, BasePort(), components)
		network.GenerateConfigTree()
		network.Bootstrap()
	})

	AfterEach(func() {
		if process != nil {
			process.Signal(syscall.SIGTERM)
			Eventually(process.Wait, network.EventuallyTimeout).Should(Receive())
		}
		if network != nil {
			network.Cleanup()
		}
		os.RemoveAll(testDir)
	})

	Context("when the docker config is bad", func() {
		It("fails the health check", func() {
			peer := network.Peer("Org1", "peer0")
			core := network.ReadPeerConfig(peer)
			core.VM.Endpoint = "127.0.0.1:0" // bad endpoint
			network.WritePeerConfig(peer, core)

			peerRunner := network.PeerRunner(peer)
			process = ginkgomon.Invoke(peerRunner)
			Eventually(process.Ready()).Should(BeClosed())

			authClient, _ := PeerOperationalClients(network, peer)
			healthURL := fmt.Sprintf("https://127.0.0.1:%d/healthz", network.PeerPort(peer, nwo.OperationsPort))
			statusCode, status := DoHealthCheck(authClient, healthURL)
			Expect(statusCode).To(Equal(http.StatusServiceUnavailable))
			Expect(status.Status).To(Equal("Service Unavailable"))
			Expect(status.FailedChecks).To(ConsistOf(
				healthz.FailedCheck{Component: "docker", Reason: "failed to connect to Docker daemon: invalid endpoint"},
			))
		})
	})

	Describe("CouchDB health checks", func() {
		var (
			couchAddr    string
			authClient   *http.Client
			healthURL    string
			peer         *nwo.Peer
			couchProcess ifrit.Process
		)

		BeforeEach(func() {
			couchDB := &runner.CouchDB{}
			couchProcess = ifrit.Invoke(couchDB)
			Eventually(couchProcess.Ready(), runner.DefaultStartTimeout).Should(BeClosed())
			Consistently(couchProcess.Wait()).ShouldNot(Receive())
			couchAddr = couchDB.Address()

			peer = network.Peer("Org1", "peer0")
			core := network.ReadPeerConfig(peer)
			core.Ledger.State.StateDatabase = "CouchDB"
			core.Ledger.State.CouchDBConfig.CouchDBAddress = couchAddr
			network.WritePeerConfig(peer, core)

			peerRunner := network.PeerRunner(peer)
			process = ginkgomon.Invoke(peerRunner)
			Eventually(process.Ready()).Should(BeClosed())

			authClient, _ = PeerOperationalClients(network, peer)
			healthURL = fmt.Sprintf("https://127.0.0.1:%d/healthz", network.PeerPort(peer, nwo.OperationsPort))
		})

		AfterEach(func() {
			couchProcess.Signal(syscall.SIGTERM)
			Eventually(couchProcess.Wait(), network.EventuallyTimeout).Should(Receive())
		})

		When("running health checks on Couch DB", func() {
			It("returns approprate response codes", func() {
				By("returning 200 when able to reach Couch DB", func() {
					statusCode, status := DoHealthCheck(authClient, healthURL)
					Expect(statusCode).To(Equal(http.StatusOK))
					Expect(status.Status).To(Equal("OK"))
				})

				By("returning 503 when unable to reach Couch DB", func() {
					couchProcess.Signal(syscall.SIGTERM)
					Eventually(couchProcess.Wait(), network.EventuallyTimeout).Should(Receive())

					var statusCode int
					var status *healthz.HealthStatus
					Eventually(func() int {
						statusCode, status = DoHealthCheck(authClient, healthURL)
						return statusCode
					}, network.EventuallyTimeout).Should(Equal(http.StatusServiceUnavailable))
					Expect(status.Status).To(Equal("Service Unavailable"))
					Expect(status.FailedChecks[0].Component).To(Equal("couchdb"))
					Expect(status.FailedChecks[0].Reason).Should((HavePrefix(fmt.Sprintf("failed to connect to couch db [Head http://%s: dial tcp %s: ", couchAddr, couchAddr))))
				})
			})
		})
	})

	Describe("Kafka health checks", func() {
		var (
			oProcess ifrit.Process
			zProcess ifrit.Process
			kProcess []ifrit.Process

			authClient *http.Client
			healthURL  string
		)

		BeforeEach(func() {
			// Start Zookeeper
			zookeepers := []string{}
			zk := network.ZooKeeperRunner(0)
			zookeepers = append(zookeepers, fmt.Sprintf("%s:2181", zk.Name))
			zProcess = ifrit.Invoke(zk)
			Eventually(zProcess.Ready(), network.EventuallyTimeout).Should(BeClosed())

			// Start Kafka Brokers
			for i := 0; i < network.Consensus.Brokers; i++ {
				kafkaRunner := network.BrokerRunner(i, zookeepers)
				kp := ifrit.Invoke(kafkaRunner)
				Eventually(kp.Ready(), network.EventuallyTimeout).Should(BeClosed())
				kProcess = append(kProcess, kp)
			}

			// Start Orderer
			ordererRunner := network.OrdererGroupRunner()
			oProcess = ifrit.Invoke(ordererRunner)
			Eventually(oProcess.Ready(), network.EventuallyTimeout).Should(BeClosed())

			orderer := network.Orderers[0]
			authClient, _ = OrdererOperationalClients(network, orderer)
			healthURL = fmt.Sprintf("https://127.0.0.1:%d/healthz", network.OrdererPort(orderer, nwo.OperationsPort))
		})

		AfterEach(func() {
			if zProcess != nil {
				zProcess.Signal(syscall.SIGTERM)
				Eventually(zProcess.Wait, network.EventuallyTimeout).Should(Receive())
			}
			if oProcess != nil {
				oProcess.Signal(syscall.SIGTERM)
				Eventually(oProcess.Wait, network.EventuallyTimeout).Should(Receive())
			}
			for _, k := range kProcess {
				if k != nil {
					k.Signal(syscall.SIGTERM)
					Eventually(k.Wait, network.EventuallyTimeout).Should(Receive())
				}
			}
		})

		Context("when running health checks on orderer the kafka health check", func() {
			It("returns appropriate response code", func() {
				By("returning 200 when all brokers are online", func() {
					statusCode, status := DoHealthCheck(authClient, healthURL)
					Expect(statusCode).To(Equal(http.StatusOK))
					Expect(status.Status).To(Equal("OK"))
				})

				By("returning a 200 when one of the three brokers goes offline", func() {
					k := kProcess[1]
					k.Signal(syscall.SIGTERM)
					Eventually(k.Wait, network.EventuallyTimeout).Should(Receive())

					var statusCode int
					var status *healthz.HealthStatus
					Consistently(func() int {
						statusCode, status = DoHealthCheck(authClient, healthURL)
						return statusCode
					}).Should(Equal(http.StatusOK))
					Expect(status.Status).To(Equal("OK"))
				})

				By("returning a 503 when two of the three brokers go offline", func() {
					k := kProcess[0]
					k.Signal(syscall.SIGTERM)
					Eventually(k.Wait, network.EventuallyTimeout).Should(Receive())

					var statusCode int
					var status *healthz.HealthStatus
					Eventually(func() int {
						statusCode, status = DoHealthCheck(authClient, healthURL)
						return statusCode
					}, network.EventuallyTimeout).Should(Equal(http.StatusServiceUnavailable))
					Expect(status.Status).To(Equal("Service Unavailable"))
					Expect(status.FailedChecks[0].Component).To(Equal("systemchannel/0"))
					Expect(status.FailedChecks[0].Reason).To(MatchRegexp(`\[replica ids: \[\d \d \d\]\]: kafka server: Messages are rejected since there are fewer in-sync replicas than required\.`))
				})
			})
		})
	})
})

func DoHealthCheck(client *http.Client, url string) (int, *healthz.HealthStatus) {
	resp, err := client.Get(url)
	Expect(err).NotTo(HaveOccurred())

	bodyBytes, err := ioutil.ReadAll(resp.Body)
	Expect(err).NotTo(HaveOccurred())
	resp.Body.Close()

	// This occurs when a request to the health check server times out, no body is
	// returned when a timeout occurs
	if len(bodyBytes) == 0 {
		return resp.StatusCode, nil
	}

	healthStatus := &healthz.HealthStatus{}
	err = json.Unmarshal(bodyBytes, healthStatus)
	Expect(err).NotTo(HaveOccurred())

	return resp.StatusCode, healthStatus
}
<commit_msg>[FAB-14463] wait for net error before healthcheck<commit_after>/*
Copyright IBM Corp All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package e2e

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"syscall"

	docker "github.com/fsouza/go-dockerclient"
	"github.com/hyperledger/fabric-lib-go/healthz"
	"github.com/hyperledger/fabric/integration/nwo"
	"github.com/hyperledger/fabric/integration/runner"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/tedsuo/ifrit"
	"github.com/tedsuo/ifrit/ginkgomon"
)

var _ = Describe("Health", func() {
	var (
		testDir string
		client  *docker.Client
		network *nwo.Network
		process ifrit.Process
	)

	BeforeEach(func() {
		var err error
		testDir, err = ioutil.TempDir("", "e2e")
		Expect(err).NotTo(HaveOccurred())

		client, err = docker.NewClientFromEnv()
		Expect(err).NotTo(HaveOccurred())

		config := nwo.BasicKafka()
		config.Consensus.Brokers = 3

		network = nwo.New(config, testDir, client, BasePort(), components)
		network.GenerateConfigTree()
		network.Bootstrap()
	})

	AfterEach(func() {
		if process != nil {
			process.Signal(syscall.SIGTERM)
			Eventually(process.Wait, network.EventuallyTimeout).Should(Receive())
		}
		if network != nil {
			network.Cleanup()
		}
		os.RemoveAll(testDir)
	})

	Context("when the docker config is bad", func() {
		It("fails the health check", func() {
			peer := network.Peer("Org1", "peer0")
			core := network.ReadPeerConfig(peer)
			core.VM.Endpoint = "127.0.0.1:0" // bad endpoint
			network.WritePeerConfig(peer, core)

			peerRunner := network.PeerRunner(peer)
			process = ginkgomon.Invoke(peerRunner)
			Eventually(process.Ready()).Should(BeClosed())

			authClient, _ := PeerOperationalClients(network, peer)
			healthURL := fmt.Sprintf("https://127.0.0.1:%d/healthz", network.PeerPort(peer, nwo.OperationsPort))
			statusCode, status := DoHealthCheck(authClient, healthURL)
			Expect(statusCode).To(Equal(http.StatusServiceUnavailable))
			Expect(status.Status).To(Equal("Service Unavailable"))
			Expect(status.FailedChecks).To(ConsistOf(
				healthz.FailedCheck{Component: "docker", Reason: "failed to connect to Docker daemon: invalid endpoint"},
			))
		})
	})

	Describe("CouchDB health checks", func() {
		var (
			couchAddr    string
			authClient   *http.Client
			healthURL    string
			peer         *nwo.Peer
			couchDB      *runner.CouchDB
			couchProcess ifrit.Process
		)

		BeforeEach(func() {
			couchDB = &runner.CouchDB{}
			couchProcess = ifrit.Invoke(couchDB)
			Eventually(couchProcess.Ready(), runner.DefaultStartTimeout).Should(BeClosed())
			Consistently(couchProcess.Wait()).ShouldNot(Receive())
			couchAddr = couchDB.Address()

			peer = network.Peer("Org1", "peer0")
			core := network.ReadPeerConfig(peer)
			core.Ledger.State.StateDatabase = "CouchDB"
			core.Ledger.State.CouchDBConfig.CouchDBAddress = couchAddr
			network.WritePeerConfig(peer, core)

			peerRunner := network.PeerRunner(peer)
			process = ginkgomon.Invoke(peerRunner)
			Eventually(process.Ready()).Should(BeClosed())

			authClient, _ = PeerOperationalClients(network, peer)
			healthURL = fmt.Sprintf("https://127.0.0.1:%d/healthz", network.PeerPort(peer, nwo.OperationsPort))
		})

		AfterEach(func() {
			couchProcess.Signal(syscall.SIGTERM)
			Eventually(couchProcess.Wait(), network.EventuallyTimeout).Should(Receive())
		})

		When("running health checks on Couch DB", func() {
			It("returns appropriate response codes", func() {
				By("returning 200 when able to reach Couch DB")
				statusCode, status := DoHealthCheck(authClient, healthURL)
				Expect(statusCode).To(Equal(http.StatusOK))
				Expect(status.Status).To(Equal("OK"))

				By("terminating CouchDB")
				couchProcess.Signal(syscall.SIGTERM)
				Eventually(couchProcess.Wait(), network.EventuallyTimeout).Should(Receive())

				By("waiting for termination to complete")
				Eventually(func() bool {
					if c, err := net.Dial("tcp", couchDB.Address()); err == nil {
						c.Close()
						return false
					}
					return true
				}, network.EventuallyTimeout).Should(BeTrue())

				By("returning 503 when unable to reach Couch DB")
				Eventually(func() int {
					statusCode, _ := DoHealthCheck(authClient, healthURL)
					return statusCode
				}, network.EventuallyTimeout).Should(Equal(http.StatusServiceUnavailable))
				statusCode, status = DoHealthCheck(authClient, healthURL)
				Expect(status.Status).To(Equal("Service Unavailable"))
				Expect(status.FailedChecks[0].Component).To(Equal("couchdb"))
				Expect(status.FailedChecks[0].Reason).Should((HavePrefix(fmt.Sprintf("failed to connect to couch db [Head http://%s: dial tcp %s: ", couchAddr, couchAddr))))
			})
		})
	})

	Describe("Kafka health checks", func() {
		var (
			oProcess ifrit.Process
			zProcess ifrit.Process
			kProcess []ifrit.Process

			authClient *http.Client
			healthURL  string
		)

		BeforeEach(func() {
			// Start Zookeeper
			zookeepers := []string{}
			zk := network.ZooKeeperRunner(0)
			zookeepers = append(zookeepers, fmt.Sprintf("%s:2181", zk.Name))
			zProcess = ifrit.Invoke(zk)
			Eventually(zProcess.Ready(), network.EventuallyTimeout).Should(BeClosed())

			// Start Kafka Brokers
			for i := 0; i < network.Consensus.Brokers; i++ {
				kafkaRunner := network.BrokerRunner(i, zookeepers)
				kp := ifrit.Invoke(kafkaRunner)
				Eventually(kp.Ready(), network.EventuallyTimeout).Should(BeClosed())
				kProcess = append(kProcess, kp)
			}

			// Start Orderer
			ordererRunner := network.OrdererGroupRunner()
			oProcess = ifrit.Invoke(ordererRunner)
			Eventually(oProcess.Ready(), network.EventuallyTimeout).Should(BeClosed())

			orderer := network.Orderers[0]
			authClient, _ = OrdererOperationalClients(network, orderer)
			healthURL = fmt.Sprintf("https://127.0.0.1:%d/healthz", network.OrdererPort(orderer, nwo.OperationsPort))
		})

		AfterEach(func() {
			if zProcess != nil {
				zProcess.Signal(syscall.SIGTERM)
				Eventually(zProcess.Wait, network.EventuallyTimeout).Should(Receive())
			}
			if oProcess != nil {
				oProcess.Signal(syscall.SIGTERM)
				Eventually(oProcess.Wait, network.EventuallyTimeout).Should(Receive())
			}
			for _, k := range kProcess {
				if k != nil {
					k.Signal(syscall.SIGTERM)
					Eventually(k.Wait, network.EventuallyTimeout).Should(Receive())
				}
			}
		})

		Context("when running health checks on orderer the kafka health check", func() {
			It("returns appropriate response code", func() {
				By("returning 200 when all brokers are online", func() {
					statusCode, status := DoHealthCheck(authClient, healthURL)
					Expect(statusCode).To(Equal(http.StatusOK))
					Expect(status.Status).To(Equal("OK"))
				})

				By("returning a 200 when one of the three brokers goes offline", func() {
					k := kProcess[1]
					k.Signal(syscall.SIGTERM)
					Eventually(k.Wait, network.EventuallyTimeout).Should(Receive())

					var statusCode int
					var status *healthz.HealthStatus
					Consistently(func() int {
						statusCode, status = DoHealthCheck(authClient, healthURL)
						return statusCode
					}).Should(Equal(http.StatusOK))
					Expect(status.Status).To(Equal("OK"))
				})

				By("returning a 503 when two of the three brokers go offline", func() {
					k := kProcess[0]
					k.Signal(syscall.SIGTERM)
					Eventually(k.Wait, network.EventuallyTimeout).Should(Receive())

					var statusCode int
					var status *healthz.HealthStatus
					Eventually(func() int {
						statusCode, status = DoHealthCheck(authClient, healthURL)
						return statusCode
					}, network.EventuallyTimeout).Should(Equal(http.StatusServiceUnavailable))
					Expect(status.Status).To(Equal("Service Unavailable"))
					Expect(status.FailedChecks[0].Component).To(Equal("systemchannel/0"))
					Expect(status.FailedChecks[0].Reason).To(MatchRegexp(`\[replica ids: \[\d \d \d\]\]: kafka server: Messages are rejected since there are fewer in-sync replicas than required\.`))
				})
			})
		})
	})
})

func DoHealthCheck(client *http.Client, url string) (int, *healthz.HealthStatus) {
	resp, err := client.Get(url)
	Expect(err).NotTo(HaveOccurred())

	bodyBytes, err := ioutil.ReadAll(resp.Body)
	Expect(err).NotTo(HaveOccurred())
	resp.Body.Close()

	// This occurs when a request to the health check server times out, no body is
	// returned when a timeout occurs
	if len(bodyBytes) == 0 {
		return resp.StatusCode, nil
	}

	healthStatus := &healthz.HealthStatus{}
	err = json.Unmarshal(bodyBytes, healthStatus)
	Expect(err).NotTo(HaveOccurred())

	return resp.StatusCode, healthStatus
}
<|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.

// +build android ios

package mobile

import (
	"context"
	"fmt"
	"runtime/debug"
	"sync"
	"sync/atomic"
	"unicode"

	"golang.org/x/mobile/app"
	"golang.org/x/mobile/event/key"
	"golang.org/x/mobile/event/lifecycle"
	"golang.org/x/mobile/event/paint"
	"golang.org/x/mobile/event/size"
	"golang.org/x/mobile/event/touch"
	"golang.org/x/mobile/gl"

	"github.com/hajimehoshi/ebiten/internal/devicescale"
	"github.com/hajimehoshi/ebiten/internal/driver"
	"github.com/hajimehoshi/ebiten/internal/graphicsdriver/opengl"
	"github.com/hajimehoshi/ebiten/internal/hooks"
	"github.com/hajimehoshi/ebiten/internal/restorable"
	"github.com/hajimehoshi/ebiten/internal/thread"
)

var (
	glContextCh = make(chan gl.Context, 1)

	// renderCh receives when updating starts.
	renderCh = make(chan struct{})

	// renderEndCh receives when updating finishes.
	renderEndCh = make(chan struct{})

	theUI = &UserInterface{
		foreground: 1,
		errCh:      make(chan error),

		// Give a default outside size so that the game can start without initializing them.
		outsideWidth:  640,
		outsideHeight: 480,
		sizeChanged:   true,
	}
)

func init() {
	theUI.input.ui = theUI
}

func Get() *UserInterface {
	return theUI
}

// Update is called from mobile/ebitenmobileview.
//
// Update must be called on the rendering thread.
func (u *UserInterface) Update() error {
	select {
	case err := <-u.errCh:
		return err
	default:
	}

	if !u.IsFocused() {
		return nil
	}

	renderCh <- struct{}{}
	ctx, cancel := context.WithCancel(context.Background())
	go func() {
		<-renderEndCh
		if u.t != nil {
			// If there is a (main) thread, ensure that cancel is called after every other task is done.
			u.t.Call(func() error {
				cancel()
				return nil
			})
		} else {
			cancel()
		}
	}()

	if u.Graphics().IsGL() {
		if u.glWorker == nil {
			panic("mobile: glWorker must be initialized but not")
		}
		workAvailable := u.glWorker.WorkAvailable()
	loop:
		for {
			select {
			case <-workAvailable:
				u.glWorker.DoWork()
			case <-ctx.Done():
				break loop
			}
		}
		return nil
	} else {
		u.t.Loop(ctx)
	}
	return nil
}

type UserInterface struct {
	outsideWidth  float64
	outsideHeight float64

	sizeChanged bool
	foreground  int32
	errCh       chan error

	// Used for gomobile-build
	gbuildWidthPx   int
	gbuildHeightPx  int
	setGBuildSizeCh chan struct{}
	once            sync.Once

	context driver.UIContext

	input Input

	t        *thread.Thread
	glWorker gl.Worker

	m sync.RWMutex
}

func deviceScale() float64 {
	return devicescale.GetAt(0, 0)
}

// appMain is the main routine for gomobile-build mode.
func (u *UserInterface) appMain(a app.App) {
	var glctx gl.Context
	var sizeInited bool

	touches := map[touch.Sequence]*Touch{}
	keys := map[driver.Key]struct{}{}

	for e := range a.Events() {
		var updateInput bool
		var runes []rune

		switch e := a.Filter(e).(type) {
		case lifecycle.Event:
			switch e.Crosses(lifecycle.StageVisible) {
			case lifecycle.CrossOn:
				u.SetForeground(true)
				restorable.OnContextLost()
				glctx, _ = e.DrawContext.(gl.Context)
				// Assume that glctx is always a same instance.
				// Then, only once initializing should be enough.
				if glContextCh != nil {
					glContextCh <- glctx
					glContextCh = nil
				}
				a.Send(paint.Event{})
			case lifecycle.CrossOff:
				u.SetForeground(false)
				glctx = nil
			}
		case size.Event:
			u.setGBuildSize(e.WidthPx, e.HeightPx)
			sizeInited = true
		case paint.Event:
			if !sizeInited {
				a.Send(paint.Event{})
				continue
			}
			if glctx == nil || e.External {
				continue
			}
			renderCh <- struct{}{}
			<-renderEndCh
			a.Publish()
			a.Send(paint.Event{})
		case touch.Event:
			if !sizeInited {
				continue
			}
			switch e.Type {
			case touch.TypeBegin, touch.TypeMove:
				s := deviceScale()
				x, y := float64(e.X)/s, float64(e.Y)/s
				// TODO: Is it ok to cast from int64 to int here?
				touches[e.Sequence] = &Touch{
					ID: int(e.Sequence),
					X:  int(x),
					Y:  int(y),
				}
			case touch.TypeEnd:
				delete(touches, e.Sequence)
			}
			updateInput = true
		case key.Event:
			k, ok := gbuildKeyToDriverKey[e.Code]
			if ok {
				switch e.Direction {
				case key.DirPress, key.DirNone:
					keys[k] = struct{}{}
				case key.DirRelease:
					delete(keys, k)
				}
			}

			switch e.Direction {
			case key.DirPress, key.DirNone:
				if e.Rune != -1 && unicode.IsPrint(e.Rune) {
					runes = []rune{e.Rune}
				}
			}
			updateInput = true
		}

		if updateInput {
			ts := []*Touch{}
			for _, t := range touches {
				ts = append(ts, t)
			}
			u.input.update(keys, runes, ts, nil)
		}
	}
}

func (u *UserInterface) SetForeground(foreground bool) {
	var v int32
	if foreground {
		v = 1
	}
	atomic.StoreInt32(&u.foreground, v)

	if foreground {
		hooks.ResumeAudio()
	} else {
		hooks.SuspendAudio()
	}
}

func (u *UserInterface) Run(context driver.UIContext) error {
	u.setGBuildSizeCh = make(chan struct{})
	go func() {
		if err := u.run(context, true); err != nil {
			// As mobile apps never ends, Loop can't return. Just panic here.
			panic(err)
		}
	}()
	app.Main(u.appMain)
	return nil
}

func (u *UserInterface) RunWithoutMainLoop(context driver.UIContext) {
	go func() {
		// title is ignored?
		if err := u.run(context, false); err != nil {
			u.errCh <- err
		}
	}()
}

func (u *UserInterface) run(context driver.UIContext, mainloop bool) (err error) {
	// Convert the panic to a regular error so that Java/Objective-C layer can treat this easily e.g., for
	// Crashlytics. A panic is treated as SIGABRT, and there is no way to handle this on Java/Objective-C layer
	// unfortunately.
	// TODO: Panic on other goroutines cannot be handled here.
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("%v\n%q", r, string(debug.Stack()))
		}
	}()

	u.m.Lock()
	u.sizeChanged = true
	u.context = context
	u.m.Unlock()

	if u.Graphics().IsGL() {
		var ctx gl.Context
		if mainloop {
			ctx = <-glContextCh
		} else {
			ctx, u.glWorker = gl.NewContext()
		}
		u.Graphics().(*opengl.Graphics).SetMobileGLContext(ctx)
	} else {
		u.t = thread.New()
		u.Graphics().SetThread(u.t)
	}

	// If gomobile-build is used, wait for the outside size fixed.
	if u.setGBuildSizeCh != nil {
		<-u.setGBuildSizeCh
	}

	// Force to set the screen size
	u.layoutIfNeeded()
	for {
		if err := u.update(); err != nil {
			return err
		}
	}
}

// layoutIfNeeded must be called on the same goroutine as update().
func (u *UserInterface) layoutIfNeeded() {
	var outsideWidth, outsideHeight float64

	u.m.Lock()
	sizeChanged := u.sizeChanged
	if sizeChanged {
		if u.gbuildWidthPx == 0 || u.gbuildHeightPx == 0 {
			outsideWidth = u.outsideWidth
			outsideHeight = u.outsideHeight
		} else {
			// gomobile build
			d := deviceScale()
			outsideWidth = float64(u.gbuildWidthPx) / d
			outsideHeight = float64(u.gbuildHeightPx) / d
		}
	}
	u.sizeChanged = false
	u.m.Unlock()

	if sizeChanged {
		u.context.Layout(outsideWidth, outsideHeight)
	}
}

func (u *UserInterface) update() error {
	<-renderCh
	defer func() {
		renderEndCh <- struct{}{}
	}()

	if err := u.context.Update(); err != nil {
		return err
	}
	if err := u.context.Draw(); err != nil {
		return err
	}
	return nil
}

func (u *UserInterface) ScreenSizeInFullscreen() (int, int) {
	// TODO: This function should return gbuildWidthPx, gbuildHeightPx,
	// but these values are not initialized until the main loop starts.
	return 0, 0
}

// SetOutsideSize is called from mobile/ebitenmobileview.
//
// SetOutsideSize is concurrent safe.
func (u *UserInterface) SetOutsideSize(outsideWidth, outsideHeight float64) {
	u.m.Lock()
	if u.outsideWidth != outsideWidth || u.outsideHeight != outsideHeight {
		u.outsideWidth = outsideWidth
		u.outsideHeight = outsideHeight
		u.sizeChanged = true
	}
	u.m.Unlock()
}

func (u *UserInterface) setGBuildSize(widthPx, heightPx int) {
	u.m.Lock()
	u.gbuildWidthPx = widthPx
	u.gbuildHeightPx = heightPx
	u.sizeChanged = true
	u.m.Unlock()

	u.once.Do(func() {
		close(u.setGBuildSizeCh)
	})
}

func (u *UserInterface) adjustPosition(x, y int) (int, int) {
	xf, yf := u.context.AdjustPosition(float64(x), float64(y))
	return int(xf), int(yf)
}

func (u *UserInterface) CursorMode() driver.CursorMode {
	return driver.CursorModeHidden
}

func (u *UserInterface) SetCursorMode(mode driver.CursorMode) {
	// Do nothing
}

func (u *UserInterface) IsFullscreen() bool {
	return false
}

func (u *UserInterface) SetFullscreen(fullscreen bool) {
	// Do nothing
}

func (u *UserInterface) IsFocused() bool {
	return atomic.LoadInt32(&u.foreground) != 0
}

func (u *UserInterface) IsRunnableOnUnfocused() bool {
	return false
}

func (u *UserInterface) SetRunnableOnUnfocused(runnableOnUnfocused bool) {
	// Do nothing
}

func (u *UserInterface) IsVsyncEnabled() bool {
	return true
}

func (u *UserInterface) SetVsyncEnabled(enabled bool) {
	// Do nothing
}

func (u *UserInterface) DeviceScaleFactor() float64 {
	return deviceScale()
}

func (u *UserInterface) SetScreenTransparent(transparent bool) {
	// Do nothing
}

func (u *UserInterface) IsScreenTransparent() bool {
	return false
}

func (u *UserInterface) ResetForFrame() {
	u.layoutIfNeeded()
	u.input.resetForFrame()
}

func (u *UserInterface) SetInitFocused(focused bool) {
	// Do nothing
}

func (u *UserInterface) Input() driver.Input {
	return &u.input
}

func (u *UserInterface) Window() driver.Window {
	return nil
}

type Touch struct {
	ID int
	X  int
	Y  int
}

type Gamepad struct {
	ID        int
	SDLID     string
	Name      string
	Buttons   [driver.GamepadButtonNum]bool
	ButtonNum int
	Axes      [32]float32
	AxisNum   int
}

func (u *UserInterface) UpdateInput(keys map[driver.Key]struct{}, runes []rune, touches []*Touch, gamepads []Gamepad) {
	u.input.update(keys, runes, touches, gamepads)
}
<commit_msg>uidriver/mobile: Refactoring<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.

// +build android ios

package mobile

import (
	"context"
	"fmt"
	"runtime/debug"
	"sync"
	"sync/atomic"
	"unicode"

	"golang.org/x/mobile/app"
	"golang.org/x/mobile/event/key"
	"golang.org/x/mobile/event/lifecycle"
	"golang.org/x/mobile/event/paint"
	"golang.org/x/mobile/event/size"
	"golang.org/x/mobile/event/touch"
	"golang.org/x/mobile/gl"

	"github.com/hajimehoshi/ebiten/internal/devicescale"
	"github.com/hajimehoshi/ebiten/internal/driver"
	"github.com/hajimehoshi/ebiten/internal/graphicsdriver/opengl"
	"github.com/hajimehoshi/ebiten/internal/hooks"
	"github.com/hajimehoshi/ebiten/internal/restorable"
	"github.com/hajimehoshi/ebiten/internal/thread"
)

var (
	glContextCh = make(chan gl.Context, 1)

	// renderCh receives when updating starts.
	renderCh = make(chan struct{})

	// renderEndCh receives when updating finishes.
	renderEndCh = make(chan struct{})

	theUI = &UserInterface{
		foreground: 1,
		errCh:      make(chan error),

		// Give a default outside size so that the game can start without initializing them.
		outsideWidth:  640,
		outsideHeight: 480,
		sizeChanged:   true,
	}
)

func init() {
	theUI.input.ui = theUI
}

func Get() *UserInterface {
	return theUI
}

// Update is called from mobile/ebitenmobileview.
//
// Update must be called on the rendering thread.
func (u *UserInterface) Update() error {
	select {
	case err := <-u.errCh:
		return err
	default:
	}

	if !u.IsFocused() {
		return nil
	}

	renderCh <- struct{}{}
	ctx, cancel := context.WithCancel(context.Background())
	go func() {
		<-renderEndCh
		if u.t != nil {
			// If there is a (main) thread, ensure that cancel is called after every other task is done.
			u.t.Call(func() error {
				cancel()
				return nil
			})
		} else {
			cancel()
		}
	}()

	if u.Graphics().IsGL() {
		if u.glWorker == nil {
			panic("mobile: glWorker must be initialized but not")
		}
		workAvailable := u.glWorker.WorkAvailable()
	loop:
		for {
			select {
			case <-workAvailable:
				u.glWorker.DoWork()
			case <-ctx.Done():
				break loop
			}
		}
		return nil
	} else {
		u.t.Loop(ctx)
	}
	return nil
}

type UserInterface struct {
	outsideWidth  float64
	outsideHeight float64

	sizeChanged bool
	foreground  int32
	errCh       chan error

	// Used for gomobile-build
	gbuildWidthPx   int
	gbuildHeightPx  int
	setGBuildSizeCh chan struct{}
	once            sync.Once

	context driver.UIContext

	input Input

	t        *thread.Thread
	glWorker gl.Worker

	m sync.RWMutex
}

func deviceScale() float64 {
	return devicescale.GetAt(0, 0)
}

// appMain is the main routine for gomobile-build mode.
func (u *UserInterface) appMain(a app.App) {
	var glctx gl.Context
	var sizeInited bool

	touches := map[touch.Sequence]*Touch{}
	keys := map[driver.Key]struct{}{}

	for e := range a.Events() {
		var updateInput bool
		var runes []rune

		switch e := a.Filter(e).(type) {
		case lifecycle.Event:
			switch e.Crosses(lifecycle.StageVisible) {
			case lifecycle.CrossOn:
				u.SetForeground(true)
				restorable.OnContextLost()
				glctx, _ = e.DrawContext.(gl.Context)
				// Assume that glctx is always a same instance.
				// Then, only once initializing should be enough.
				if glContextCh != nil {
					glContextCh <- glctx
					glContextCh = nil
				}
				a.Send(paint.Event{})
			case lifecycle.CrossOff:
				u.SetForeground(false)
				glctx = nil
			}
		case size.Event:
			u.setGBuildSize(e.WidthPx, e.HeightPx)
			sizeInited = true
		case paint.Event:
			if !sizeInited {
				a.Send(paint.Event{})
				continue
			}
			if glctx == nil || e.External {
				continue
			}
			renderCh <- struct{}{}
			<-renderEndCh
			a.Publish()
			a.Send(paint.Event{})
		case touch.Event:
			if !sizeInited {
				continue
			}
			switch e.Type {
			case touch.TypeBegin, touch.TypeMove:
				s := deviceScale()
				x, y := float64(e.X)/s, float64(e.Y)/s
				// TODO: Is it ok to cast from int64 to int here?
				touches[e.Sequence] = &Touch{
					ID: int(e.Sequence),
					X:  int(x),
					Y:  int(y),
				}
			case touch.TypeEnd:
				delete(touches, e.Sequence)
			}
			updateInput = true
		case key.Event:
			k, ok := gbuildKeyToDriverKey[e.Code]
			if ok {
				switch e.Direction {
				case key.DirPress, key.DirNone:
					keys[k] = struct{}{}
				case key.DirRelease:
					delete(keys, k)
				}
			}

			switch e.Direction {
			case key.DirPress, key.DirNone:
				if e.Rune != -1 && unicode.IsPrint(e.Rune) {
					runes = []rune{e.Rune}
				}
			}
			updateInput = true
		}

		if updateInput {
			ts := []*Touch{}
			for _, t := range touches {
				ts = append(ts, t)
			}
			u.input.update(keys, runes, ts, nil)
		}
	}
}

func (u *UserInterface) SetForeground(foreground bool) {
	var v int32
	if foreground {
		v = 1
	}
	atomic.StoreInt32(&u.foreground, v)

	if foreground {
		hooks.ResumeAudio()
	} else {
		hooks.SuspendAudio()
	}
}

func (u *UserInterface) Run(context driver.UIContext) error {
	u.setGBuildSizeCh = make(chan struct{})
	go func() {
		if err := u.run(context, true); err != nil {
			// As mobile apps never ends, Loop can't return. Just panic here.
			panic(err)
		}
	}()
	app.Main(u.appMain)
	return nil
}

func (u *UserInterface) RunWithoutMainLoop(context driver.UIContext) {
	go func() {
		// title is ignored?
		if err := u.run(context, false); err != nil {
			u.errCh <- err
		}
	}()
}

func (u *UserInterface) run(context driver.UIContext, mainloop bool) (err error) {
	// Convert the panic to a regular error so that Java/Objective-C layer can treat this easily e.g., for
	// Crashlytics. A panic is treated as SIGABRT, and there is no way to handle this on Java/Objective-C layer
	// unfortunately.
	// TODO: Panic on other goroutines cannot be handled here.
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("%v\n%q", r, string(debug.Stack()))
		}
	}()

	u.m.Lock()
	u.sizeChanged = true
	u.m.Unlock()

	u.context = context

	if u.Graphics().IsGL() {
		var ctx gl.Context
		if mainloop {
			ctx = <-glContextCh
		} else {
			ctx, u.glWorker = gl.NewContext()
		}
		u.Graphics().(*opengl.Graphics).SetMobileGLContext(ctx)
	} else {
		u.t = thread.New()
		u.Graphics().SetThread(u.t)
	}

	// If gomobile-build is used, wait for the outside size fixed.
	if u.setGBuildSizeCh != nil {
		<-u.setGBuildSizeCh
	}

	// Force to set the screen size
	u.layoutIfNeeded()
	for {
		if err := u.update(); err != nil {
			return err
		}
	}
}

// layoutIfNeeded must be called on the same goroutine as update().
func (u *UserInterface) layoutIfNeeded() {
	var outsideWidth, outsideHeight float64

	u.m.RLock()
	sizeChanged := u.sizeChanged
	if sizeChanged {
		if u.gbuildWidthPx == 0 || u.gbuildHeightPx == 0 {
			outsideWidth = u.outsideWidth
			outsideHeight = u.outsideHeight
		} else {
			// gomobile build
			d := deviceScale()
			outsideWidth = float64(u.gbuildWidthPx) / d
			outsideHeight = float64(u.gbuildHeightPx) / d
		}
	}
	u.sizeChanged = false
	u.m.RUnlock()

	if sizeChanged {
		u.context.Layout(outsideWidth, outsideHeight)
	}
}

func (u *UserInterface) update() error {
	<-renderCh
	defer func() {
		renderEndCh <- struct{}{}
	}()

	if err := u.context.Update(); err != nil {
		return err
	}
	if err := u.context.Draw(); err != nil {
		return err
	}
	return nil
}

func (u *UserInterface) ScreenSizeInFullscreen() (int, int) {
	// TODO: This function should return gbuildWidthPx, gbuildHeightPx,
	// but these values are not initialized until the main loop starts.
	return 0, 0
}

// SetOutsideSize is called from mobile/ebitenmobileview.
//
// SetOutsideSize is concurrent safe.
func (u *UserInterface) SetOutsideSize(outsideWidth, outsideHeight float64) {
	u.m.Lock()
	if u.outsideWidth != outsideWidth || u.outsideHeight != outsideHeight {
		u.outsideWidth = outsideWidth
		u.outsideHeight = outsideHeight
		u.sizeChanged = true
	}
	u.m.Unlock()
}

func (u *UserInterface) setGBuildSize(widthPx, heightPx int) {
	u.m.Lock()
	u.gbuildWidthPx = widthPx
	u.gbuildHeightPx = heightPx
	u.sizeChanged = true
	u.m.Unlock()

	u.once.Do(func() {
		close(u.setGBuildSizeCh)
	})
}

func (u *UserInterface) adjustPosition(x, y int) (int, int) {
	xf, yf := u.context.AdjustPosition(float64(x), float64(y))
	return int(xf), int(yf)
}

func (u *UserInterface) CursorMode() driver.CursorMode {
	return driver.CursorModeHidden
}

func (u *UserInterface) SetCursorMode(mode driver.CursorMode) {
	// Do nothing
}

func (u *UserInterface) IsFullscreen() bool {
	return false
}

func (u *UserInterface) SetFullscreen(fullscreen bool) {
	// Do nothing
}

func (u *UserInterface) IsFocused() bool {
	return atomic.LoadInt32(&u.foreground) != 0
}

func (u *UserInterface) IsRunnableOnUnfocused() bool {
	return false
}

func (u *UserInterface) SetRunnableOnUnfocused(runnableOnUnfocused bool) {
	// Do nothing
}

func (u *UserInterface) IsVsyncEnabled() bool {
	return true
}

func (u *UserInterface) SetVsyncEnabled(enabled bool) {
	// Do nothing
}

func (u *UserInterface) DeviceScaleFactor() float64 {
	return deviceScale()
}

func (u *UserInterface) SetScreenTransparent(transparent bool) {
	// Do nothing
}

func (u *UserInterface) IsScreenTransparent() bool {
	return false
}

func (u *UserInterface) ResetForFrame() {
	u.layoutIfNeeded()
	u.input.resetForFrame()
}

func (u *UserInterface) SetInitFocused(focused bool) {
	// Do nothing
}

func (u *UserInterface) Input() driver.Input {
	return &u.input
}

func (u *UserInterface) Window() driver.Window {
	return nil
}

type Touch struct {
	ID int
	X  int
	Y  int
}

type Gamepad struct {
	ID        int
	SDLID     string
	Name      string
	Buttons   [driver.GamepadButtonNum]bool
	ButtonNum int
	Axes      [32]float32
	AxisNum   int
}

func (u *UserInterface) UpdateInput(keys map[driver.Key]struct{}, runes []rune, touches []*Touch, gamepads []Gamepad) {
	u.input.update(keys, runes, touches, gamepads)
}
<|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.

//go:build android || ios
// +build android ios

package mobile

import (
	"fmt"
	"runtime/debug"
	"sync"
	"sync/atomic"
	"unicode"

	"golang.org/x/mobile/app"
	"golang.org/x/mobile/event/key"
	"golang.org/x/mobile/event/lifecycle"
	"golang.org/x/mobile/event/paint"
	"golang.org/x/mobile/event/size"
	"golang.org/x/mobile/event/touch"
	"golang.org/x/mobile/gl"

	"github.com/hajimehoshi/ebiten/v2/internal/devicescale"
	"github.com/hajimehoshi/ebiten/v2/internal/driver"
	"github.com/hajimehoshi/ebiten/v2/internal/graphicscommand"
	"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/opengl"
	"github.com/hajimehoshi/ebiten/v2/internal/hooks"
	"github.com/hajimehoshi/ebiten/v2/internal/restorable"
	"github.com/hajimehoshi/ebiten/v2/internal/thread"
)

var (
	glContextCh = make(chan gl.Context, 1)

	// renderCh receives when updating starts.
	renderCh = make(chan struct{})

	// renderEndCh receives when updating finishes.
	renderEndCh = make(chan struct{})

	theUI = &UserInterface{
		foreground: 1,
		errCh:      make(chan error),

		// Give a default outside size so that the game can start without initializing them.
		outsideWidth:  640,
		outsideHeight: 480,
		sizeChanged:   true,
	}
)

func init() {
	theUI.input.ui = theUI
}

func Get() *UserInterface {
	return theUI
}

// Update is called from mobile/ebitenmobileview.
//
// Update must be called on the rendering thread.
func (u *UserInterface) Update() error {
	select {
	case err := <-u.errCh:
		return err
	default:
	}

	if !u.IsFocused() {
		return nil
	}

	u.input.updateGamepads()

	renderCh <- struct{}{}
	go func() {
		<-renderEndCh
		u.t.Call(func() error {
			return thread.BreakLoop
		})
	}()
	u.t.Loop()
	return nil
}

type UserInterface struct {
	outsideWidth  float64
	outsideHeight float64

	sizeChanged bool
	foreground  int32
	errCh       chan error

	// Used for gomobile-build
	gbuildWidthPx   int
	gbuildHeightPx  int
	setGBuildSizeCh chan struct{}
	once            sync.Once

	context driver.UIContext

	input Input

	fpsMode         driver.FPSMode
	renderRequester RenderRequester

	t *thread.OSThread

	m sync.RWMutex
}

func deviceScale() float64 {
	return devicescale.GetAt(0, 0)
}

// appMain is the main routine for gomobile-build mode.
func (u *UserInterface) appMain(a app.App) {
	var glctx gl.Context
	var sizeInited bool

	touches := map[touch.Sequence]Touch{}
	keys := map[driver.Key]struct{}{}

	for e := range a.Events() {
		var updateInput bool
		var runes []rune

		switch e := a.Filter(e).(type) {
		case lifecycle.Event:
			switch e.Crosses(lifecycle.StageVisible) {
			case lifecycle.CrossOn:
				if err := u.SetForeground(true); err != nil {
					// There are no other ways than panicking here.
					panic(err)
				}
				restorable.OnContextLost()
				glctx, _ = e.DrawContext.(gl.Context)
				// Assume that glctx is always a same instance.
				// Then, only once initializing should be enough.
				if glContextCh != nil {
					glContextCh <- glctx
					glContextCh = nil
				}
				a.Send(paint.Event{})
			case lifecycle.CrossOff:
				if err := u.SetForeground(false); err != nil {
					// There are no other ways than panicking here.
					panic(err)
				}
				glctx = nil
			}
		case size.Event:
			u.setGBuildSize(e.WidthPx, e.HeightPx)
			sizeInited = true
		case paint.Event:
			if !sizeInited {
				a.Send(paint.Event{})
				continue
			}
			if glctx == nil || e.External {
				continue
			}
			renderCh <- struct{}{}
			<-renderEndCh
			a.Publish()
			a.Send(paint.Event{})
		case touch.Event:
			if !sizeInited {
				continue
			}
			switch e.Type {
			case touch.TypeBegin, touch.TypeMove:
				s := deviceScale()
				x, y := float64(e.X)/s, float64(e.Y)/s
				// TODO: Is it ok to cast from int64 to int here?
				touches[e.Sequence] = Touch{
					ID: driver.TouchID(e.Sequence),
					X:  int(x),
					Y:  int(y),
				}
			case touch.TypeEnd:
				delete(touches, e.Sequence)
			}
			updateInput = true
		case key.Event:
			k, ok := gbuildKeyToDriverKey[e.Code]
			if ok {
				switch e.Direction {
				case key.DirPress, key.DirNone:
					keys[k] = struct{}{}
				case key.DirRelease:
					delete(keys, k)
				}
			}

			switch e.Direction {
			case key.DirPress, key.DirNone:
				if e.Rune != -1 && unicode.IsPrint(e.Rune) {
					runes = []rune{e.Rune}
				}
			}
			updateInput = true
		}

		if updateInput {
			var ts []Touch
			for _, t := range touches {
				ts = append(ts, t)
			}
			u.input.update(keys, runes, ts)
		}
	}
}

func (u *UserInterface) SetForeground(foreground bool) error {
	var v int32
	if foreground {
		v = 1
	}
	atomic.StoreInt32(&u.foreground, v)

	if foreground {
		return hooks.ResumeAudio()
	} else {
		return hooks.SuspendAudio()
	}
}

func (u *UserInterface) Run(context driver.UIContext) error {
	u.setGBuildSizeCh = make(chan struct{})
	go func() {
		if err := u.run(context, true); err != nil {
			// As mobile apps never ends, Loop can't return. Just panic here.
			panic(err)
		}
	}()
	app.Main(u.appMain)
	return nil
}

func (u *UserInterface) RunWithoutMainLoop(context driver.UIContext) {
	go func() {
		if err := u.run(context, false); err != nil {
			u.errCh <- err
		}
	}()
}

func (u *UserInterface) run(context driver.UIContext, mainloop bool) (err error) {
	// Convert the panic to a regular error so that Java/Objective-C layer can treat this easily e.g., for
	// Crashlytics. A panic is treated as SIGABRT, and there is no way to handle this on Java/Objective-C layer
	// unfortunately.
	// TODO: Panic on other goroutines cannot be handled here.
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("%v\n%s", r, string(debug.Stack()))
		}
	}()

	u.m.Lock()
	u.sizeChanged = true
	u.m.Unlock()

	u.context = context

	if mainloop {
		// When mainloop is true, gomobile-build is used. In this case, GL functions must be called via
		// gl.Context so that they are called on the appropriate thread.
		ctx := <-glContextCh
		u.Graphics().(*opengl.Graphics).SetGomobileGLContext(ctx)
	} else {
		u.t = thread.NewOSThread()
		graphicscommand.SetMainThread(u.t)
	}

	// If gomobile-build is used, wait for the outside size fixed.
	if u.setGBuildSizeCh != nil {
		<-u.setGBuildSizeCh
	}

	// Force to set the screen size
	u.layoutIfNeeded()
	for {
		if err := u.update(); err != nil {
			return err
		}
	}
}

// layoutIfNeeded must be called on the same goroutine as update().
func (u *UserInterface) layoutIfNeeded() {
	var outsideWidth, outsideHeight float64

	u.m.RLock()
	sizeChanged := u.sizeChanged
	if sizeChanged {
		if u.gbuildWidthPx == 0 || u.gbuildHeightPx == 0 {
			outsideWidth = u.outsideWidth
			outsideHeight = u.outsideHeight
		} else {
			// gomobile build
			d := deviceScale()
			outsideWidth = float64(u.gbuildWidthPx) / d
			outsideHeight = float64(u.gbuildHeightPx) / d
		}
	}
	u.sizeChanged = false
	u.m.RUnlock()

	if sizeChanged {
		u.context.Layout(outsideWidth, outsideHeight)
	}
}

func (u *UserInterface) update() error {
	<-renderCh
	defer func() {
		renderEndCh <- struct{}{}
	}()

	if err := u.context.UpdateFrame(); err != nil {
		return err
	}
	return nil
}

func (u *UserInterface) ScreenSizeInFullscreen() (int, int) {
	// TODO: This function should return gbuildWidthPx, gbuildHeightPx,
	// but these values are not initialized until the main loop starts.
	return 0, 0
}

// SetOutsideSize is called from mobile/ebitenmobileview.
//
// SetOutsideSize is concurrent safe.
func (u *UserInterface) SetOutsideSize(outsideWidth, outsideHeight float64) {
	u.m.Lock()
	if u.outsideWidth != outsideWidth || u.outsideHeight != outsideHeight {
		u.outsideWidth = outsideWidth
		u.outsideHeight = outsideHeight
		u.sizeChanged = true
	}
	u.m.Unlock()
}

func (u *UserInterface) setGBuildSize(widthPx, heightPx int) {
	u.m.Lock()
	u.gbuildWidthPx = widthPx
	u.gbuildHeightPx = heightPx
	u.sizeChanged = true
	u.m.Unlock()

	u.once.Do(func() {
		close(u.setGBuildSizeCh)
	})
}

func (u *UserInterface) adjustPosition(x, y int) (int, int) {
	xf, yf := u.context.AdjustPosition(float64(x), float64(y), deviceScale())
	return int(xf), int(yf)
}

func (u *UserInterface) CursorMode() driver.CursorMode {
	return driver.CursorModeHidden
}

func (u *UserInterface) SetCursorMode(mode driver.CursorMode) {
	// Do nothing
}

func (u *UserInterface) CursorShape() driver.CursorShape {
	return driver.CursorShapeDefault
}

func (u *UserInterface) SetCursorShape(shape driver.CursorShape) {
	// Do nothing
}

func (u *UserInterface) IsFullscreen() bool {
	return false
}

func (u *UserInterface) SetFullscreen(fullscreen bool) {
	// Do nothing
}

func (u *UserInterface) IsFocused() bool {
	return atomic.LoadInt32(&u.foreground) != 0
}

func (u *UserInterface) IsRunnableOnUnfocused() bool {
	return false
}

func (u *UserInterface) SetRunnableOnUnfocused(runnableOnUnfocused bool) {
	// Do nothing
}

func (u *UserInterface) FPSMode() driver.FPSMode {
	return u.fpsMode
}

func (u *UserInterface) SetFPSMode(mode driver.FPSMode) {
	u.fpsMode = mode
	u.updateExplicitRenderingModeIfNeeded()
}

func (u *UserInterface) updateExplicitRenderingModeIfNeeded() {
	if u.renderRequester == nil {
		return
	}
	u.renderRequester.SetExplicitRenderingMode(u.fpsMode == driver.FPSModeVsyncOffMinimum)
}

func (u *UserInterface) DeviceScaleFactor() float64 {
	return deviceScale()
}

func (u *UserInterface) SetScreenTransparent(transparent bool) {
	// Do nothing
}

func (u *UserInterface) IsScreenTransparent() bool {
	return false
}

func (u *UserInterface) ResetForFrame() {
	u.layoutIfNeeded()
	u.input.resetForFrame()
}

func (u *UserInterface) SetInitFocused(focused bool) {
	// Do nothing
}

func (u *UserInterface) Input() driver.Input {
	return &u.input
}

func (u *UserInterface) Window() driver.Window {
	return nil
}

type Touch struct {
	ID driver.TouchID
	X  int
	Y  int
}

type Gamepad struct {
	ID        driver.GamepadID
	SDLID     string
	Name      string
	Buttons   [driver.GamepadButtonNum]bool
	ButtonNum int
	Axes      [32]float32
	AxisNum   int
}

func (u *UserInterface) UpdateInput(keys map[driver.Key]struct{}, runes []rune, touches []Touch) {
	u.input.update(keys, runes, touches)
	if u.fpsMode == driver.FPSModeVsyncOffMinimum {
		u.renderRequester.RequestRenderIfNeeded()
	}
}

type RenderRequester interface {
	SetExplicitRenderingMode(explicitRendering bool)
	RequestRenderIfNeeded()
}

func (u *UserInterface) SetRenderRequester(renderRequester RenderRequester) {
	u.renderRequester = renderRequester
	u.updateExplicitRenderingModeIfNeeded()
}

func (u *UserInterface) ScheduleFrame() {
	if u.renderRequester != nil && u.fpsMode == driver.FPSModeVsyncOffMinimum {
		u.renderRequester.RequestRenderIfNeeded()
	}
}

func (u *UserInterface) Vibrate(duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
	// TODO: Implement this (#1452)
}
<commit_msg>internal/uidriver/mobile: Bug fix: Compile error<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.

//go:build android || ios
// +build android ios

package mobile

import (
	"fmt"
	"runtime/debug"
	"sync"
	"sync/atomic"
	"time"
	"unicode"

	"golang.org/x/mobile/app"
	"golang.org/x/mobile/event/key"
	"golang.org/x/mobile/event/lifecycle"
	"golang.org/x/mobile/event/paint"
	"golang.org/x/mobile/event/size"
	"golang.org/x/mobile/event/touch"
	"golang.org/x/mobile/gl"

	"github.com/hajimehoshi/ebiten/v2/internal/devicescale"
	"github.com/hajimehoshi/ebiten/v2/internal/driver"
	"github.com/hajimehoshi/ebiten/v2/internal/graphicscommand"
	"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/opengl"
	"github.com/hajimehoshi/ebiten/v2/internal/hooks"
	"github.com/hajimehoshi/ebiten/v2/internal/restorable"
	"github.com/hajimehoshi/ebiten/v2/internal/thread"
)

var (
	glContextCh = make(chan gl.Context, 1)

	// renderCh receives when updating starts.
	renderCh = make(chan struct{})

	// renderEndCh receives when updating finishes.
	renderEndCh = make(chan struct{})

	theUI = &UserInterface{
		foreground: 1,
		errCh:      make(chan error),

		// Give a default outside size so that the game can start without initializing them.
		outsideWidth:  640,
		outsideHeight: 480,
		sizeChanged:   true,
	}
)

func init() {
	theUI.input.ui = theUI
}

func Get() *UserInterface {
	return theUI
}

// Update is called from mobile/ebitenmobileview.
//
// Update must be called on the rendering thread.
func (u *UserInterface) Update() error {
	select {
	case err := <-u.errCh:
		return err
	default:
	}

	if !u.IsFocused() {
		return nil
	}

	u.input.updateGamepads()

	renderCh <- struct{}{}
	go func() {
		<-renderEndCh
		u.t.Call(func() error {
			return thread.BreakLoop
		})
	}()
	u.t.Loop()
	return nil
}

type UserInterface struct {
	outsideWidth  float64
	outsideHeight float64

	sizeChanged bool
	foreground  int32
	errCh       chan error

	// Used for gomobile-build
	gbuildWidthPx   int
	gbuildHeightPx  int
	setGBuildSizeCh chan struct{}
	once            sync.Once

	context driver.UIContext

	input Input

	fpsMode         driver.FPSMode
	renderRequester RenderRequester

	t *thread.OSThread

	m sync.RWMutex
}

func deviceScale() float64 {
	return devicescale.GetAt(0, 0)
}

// appMain is the main routine for gomobile-build mode.
func (u *UserInterface) appMain(a app.App) {
	var glctx gl.Context
	var sizeInited bool

	touches := map[touch.Sequence]Touch{}
	keys := map[driver.Key]struct{}{}

	for e := range a.Events() {
		var updateInput bool
		var runes []rune

		switch e := a.Filter(e).(type) {
		case lifecycle.Event:
			switch e.Crosses(lifecycle.StageVisible) {
			case lifecycle.CrossOn:
				if err := u.SetForeground(true); err != nil {
					// There are no other ways than panicking here.
					panic(err)
				}
				restorable.OnContextLost()
				glctx, _ = e.DrawContext.(gl.Context)
				// Assume that glctx is always a same instance.
				// Then, only once initializing should be enough.
				if glContextCh != nil {
					glContextCh <- glctx
					glContextCh = nil
				}
				a.Send(paint.Event{})
			case lifecycle.CrossOff:
				if err := u.SetForeground(false); err != nil {
					// There are no other ways than panicking here.
					panic(err)
				}
				glctx = nil
			}
		case size.Event:
			u.setGBuildSize(e.WidthPx, e.HeightPx)
			sizeInited = true
		case paint.Event:
			if !sizeInited {
				a.Send(paint.Event{})
				continue
			}
			if glctx == nil || e.External {
				continue
			}
			renderCh <- struct{}{}
			<-renderEndCh
			a.Publish()
			a.Send(paint.Event{})
		case touch.Event:
			if !sizeInited {
				continue
			}
			switch e.Type {
			case touch.TypeBegin, touch.TypeMove:
				s := deviceScale()
				x, y := float64(e.X)/s, float64(e.Y)/s
				// TODO: Is it ok to cast from int64 to int here?
				touches[e.Sequence] = Touch{
					ID: driver.TouchID(e.Sequence),
					X:  int(x),
					Y:  int(y),
				}
			case touch.TypeEnd:
				delete(touches, e.Sequence)
			}
			updateInput = true
		case key.Event:
			k, ok := gbuildKeyToDriverKey[e.Code]
			if ok {
				switch e.Direction {
				case key.DirPress, key.DirNone:
					keys[k] = struct{}{}
				case key.DirRelease:
					delete(keys, k)
				}
			}

			switch e.Direction {
			case key.DirPress, key.DirNone:
				if e.Rune != -1 && unicode.IsPrint(e.Rune) {
					runes = []rune{e.Rune}
				}
			}
			updateInput = true
		}

		if updateInput {
			var ts []Touch
			for _, t := range touches {
				ts = append(ts, t)
			}
			u.input.update(keys, runes, ts)
		}
	}
}

func (u *UserInterface) SetForeground(foreground bool) error {
	var v int32
	if foreground {
		v = 1
	}
	atomic.StoreInt32(&u.foreground, v)

	if foreground {
		return hooks.ResumeAudio()
	} else {
		return hooks.SuspendAudio()
	}
}

func (u *UserInterface) Run(context driver.UIContext) error {
	u.setGBuildSizeCh = make(chan struct{})
	go func() {
		if err := u.run(context, true); err != nil {
			// As mobile apps never ends, Loop can't return. Just panic here.
			panic(err)
		}
	}()
	app.Main(u.appMain)
	return nil
}

func (u *UserInterface) RunWithoutMainLoop(context driver.UIContext) {
	go func() {
		if err := u.run(context, false); err != nil {
			u.errCh <- err
		}
	}()
}

func (u *UserInterface) run(context driver.UIContext, mainloop bool) (err error) {
	// Convert the panic to a regular error so that Java/Objective-C layer can treat this easily e.g., for
	// Crashlytics. A panic is treated as SIGABRT, and there is no way to handle this on Java/Objective-C layer
	// unfortunately.
	// TODO: Panic on other goroutines cannot be handled here.
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("%v\n%s", r, string(debug.Stack()))
		}
	}()

	u.m.Lock()
	u.sizeChanged = true
	u.m.Unlock()

	u.context = context

	if mainloop {
		// When mainloop is true, gomobile-build is used. In this case, GL functions must be called via
		// gl.Context so that they are called on the appropriate thread.
		ctx := <-glContextCh
		u.Graphics().(*opengl.Graphics).SetGomobileGLContext(ctx)
	} else {
		u.t = thread.NewOSThread()
		graphicscommand.SetMainThread(u.t)
	}

	// If gomobile-build is used, wait for the outside size fixed.
	if u.setGBuildSizeCh != nil {
		<-u.setGBuildSizeCh
	}

	// Force to set the screen size
	u.layoutIfNeeded()
	for {
		if err := u.update(); err != nil {
			return err
		}
	}
}

// layoutIfNeeded must be called on the same goroutine as update().
func (u *UserInterface) layoutIfNeeded() {
	var outsideWidth, outsideHeight float64

	u.m.RLock()
	sizeChanged := u.sizeChanged
	if sizeChanged {
		if u.gbuildWidthPx == 0 || u.gbuildHeightPx == 0 {
			outsideWidth = u.outsideWidth
			outsideHeight = u.outsideHeight
		} else {
			// gomobile build
			d := deviceScale()
			outsideWidth = float64(u.gbuildWidthPx) / d
			outsideHeight = float64(u.gbuildHeightPx) / d
		}
	}
	u.sizeChanged = false
	u.m.RUnlock()

	if sizeChanged {
		u.context.Layout(outsideWidth, outsideHeight)
	}
}

func (u *UserInterface) update() error {
	<-renderCh
	defer func() {
		renderEndCh <- struct{}{}
	}()

	if err := u.context.UpdateFrame(); err != nil {
		return err
	}
	return nil
}

func (u *UserInterface) ScreenSizeInFullscreen() (int, int) {
	// TODO: This function should return gbuildWidthPx, gbuildHeightPx,
	// but these values are not initialized until the main loop starts.
	return 0, 0
}

// SetOutsideSize is called from mobile/ebitenmobileview.
//
// SetOutsideSize is concurrent safe.
func (u *UserInterface) SetOutsideSize(outsideWidth, outsideHeight float64) {
	u.m.Lock()
	if u.outsideWidth != outsideWidth || u.outsideHeight != outsideHeight {
		u.outsideWidth = outsideWidth
		u.outsideHeight = outsideHeight
		u.sizeChanged = true
	}
	u.m.Unlock()
}

func (u *UserInterface) setGBuildSize(widthPx, heightPx int) {
	u.m.Lock()
	u.gbuildWidthPx = widthPx
	u.gbuildHeightPx = heightPx
	u.sizeChanged = true
	u.m.Unlock()

	u.once.Do(func() {
		close(u.setGBuildSizeCh)
	})
}

func (u *UserInterface) adjustPosition(x, y int) (int, int) {
	xf, yf := u.context.AdjustPosition(float64(x), float64(y), deviceScale())
	return int(xf), int(yf)
}

func (u *UserInterface) CursorMode() driver.CursorMode {
	return driver.CursorModeHidden
}

func (u *UserInterface) SetCursorMode(mode driver.CursorMode) {
	// Do nothing
}

func (u *UserInterface) CursorShape() driver.CursorShape {
	return driver.CursorShapeDefault
}

func (u *UserInterface) SetCursorShape(shape driver.CursorShape) {
	// Do nothing
}

func (u *UserInterface) IsFullscreen() bool {
	return false
}

func (u *UserInterface) SetFullscreen(fullscreen bool) {
	// Do nothing
}

func (u *UserInterface) IsFocused() bool {
	return atomic.LoadInt32(&u.foreground) != 0
}

func (u *UserInterface) IsRunnableOnUnfocused() bool {
	return false
}

func (u *UserInterface) SetRunnableOnUnfocused(runnableOnUnfocused bool) {
	// Do nothing
}

func (u *UserInterface) FPSMode() driver.FPSMode {
	return u.fpsMode
}

func (u *UserInterface) SetFPSMode(mode driver.FPSMode) {
	u.fpsMode = mode
	u.updateExplicitRenderingModeIfNeeded()
}

func (u *UserInterface) updateExplicitRenderingModeIfNeeded() {
	if u.renderRequester == nil {
		return
	}
	u.renderRequester.SetExplicitRenderingMode(u.fpsMode == driver.FPSModeVsyncOffMinimum)
}

func (u *UserInterface) DeviceScaleFactor() float64 {
	return deviceScale()
}

func (u *UserInterface) SetScreenTransparent(transparent bool) {
	// Do nothing
}

func (u *UserInterface) IsScreenTransparent() bool {
	return false
}

func (u *UserInterface) ResetForFrame() {
	u.layoutIfNeeded()
	u.input.resetForFrame()
}

func (u *UserInterface) SetInitFocused(focused bool) {
	// Do nothing
}

func (u *UserInterface) Input() driver.Input {
	return &u.input
}

func (u *UserInterface) Window() driver.Window {
	return nil
}

type Touch struct {
	ID driver.TouchID
	X  int
	Y  int
}

type Gamepad struct {
	ID        driver.GamepadID
	SDLID     string
	Name      string
	Buttons   [driver.GamepadButtonNum]bool
	ButtonNum int
	Axes      [32]float32
	AxisNum   int
}

func (u *UserInterface) UpdateInput(keys map[driver.Key]struct{}, runes []rune, touches []Touch) {
	u.input.update(keys, runes, touches)
	if u.fpsMode == driver.FPSModeVsyncOffMinimum {
		u.renderRequester.RequestRenderIfNeeded()
	}
}

type RenderRequester interface {
	SetExplicitRenderingMode(explicitRendering bool)
	RequestRenderIfNeeded()
}

func (u *UserInterface) SetRenderRequester(renderRequester RenderRequester) {
	u.renderRequester = renderRequester
	u.updateExplicitRenderingModeIfNeeded()
}

func (u *UserInterface) ScheduleFrame() {
	if u.renderRequester != nil && u.fpsMode == driver.FPSModeVsyncOffMinimum {
		u.renderRequester.RequestRenderIfNeeded()
	}
}

func (u *UserInterface) Vibrate(duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
	// TODO: Implement this (#1452)
}
<|endoftext|>


<commit_before>// Copyright (c) 2014, Rob Thornton
// All rights reserved.
// This source code is governed by a Simplied BSD-License. Please see the
// LICENSE included in this distribution for a copy of the full license
// or, if one is not included, you may also find a copy at
// http://opensource.org/licenses/BSD-2-Clause

package scan

import (
	"unicode"

	"github.com/rthornton128/calc/token"
)

type Scanner struct {
	ch      rune
	offset  int
	roffset int
	src     string
	file    *token.File
}

func (s *Scanner) Init(file *token.File, src string) {
	s.file = file
	s.offset, s.roffset = 0, 0
	s.src = src

	s.next()
}

func (s *Scanner) Scan() (lit string, tok token.Token, pos token.Pos) {
	s.skipWhitespace()

	if unicode.IsLetter(s.ch) {
		return s.scanIdentifier()
	}

	if unicode.IsDigit(s.ch) {
		return s.scanNumber()
	}

	ch := s.ch
	lit, pos = string(s.ch), s.file.Pos(s.offset)
	s.next()
	switch ch {
	case '(':
		tok = token.LPAREN
	case ')':
		tok = token.RPAREN
	case ',':
		tok = token.COMMA
	case '+':
		tok = token.ADD
	case '-':
		tok = token.SUB
	case '*':
		tok = token.MUL
	case '/':
		tok = token.QUO
	case '%':
		tok = token.REM
	case '<':
		if s.ch == '=' {
			tok = token.LTE
			s.next()
		} else {
			tok = token.LST
		}
	case '>':
		if s.ch == '=' {
			tok = token.GTE
			s.next()
		} else {
			tok = token.GTT
		}
	case '=':
		if s.ch == '=' {
			tok = token.EQL
			s.next()
		} else {
			tok = token.ASSIGN
		}
	case '!':
		if s.ch == '=' {
			tok = token.NEQ
			s.next()
		} else {
			tok = token.ILLEGAL
		}
	case '&':
		if s.ch == '&' {
			tok = token.AND
			s.next()
		} else {
			tok = token.ILLEGAL
		}
	case '|':
		if s.ch == '|' {
			tok = token.OR
			s.next()
		} else {
			tok = token.ILLEGAL
		}
	case ';':
		s.skipComment()
		s.next()
		return s.Scan()
	default:
		if s.offset >= len(s.src)-1 {
			tok = token.EOF
		} else {
			tok = token.ILLEGAL
		}
	}

	return
}

func (s *Scanner) next() {
	s.ch = rune(0)
	if s.roffset < len(s.src) {
		s.offset = s.roffset
		if s.ch == '\n' || s.offset >= len(s.src)-1 {
			s.file.AddLine(token.Pos(s.offset))
		}
		s.ch = rune(s.src[s.offset])
		s.roffset++
	}
}

func (s *Scanner) scanIdentifier() (string, token.Token, token.Pos) {
	start := s.offset

	for unicode.IsLetter(s.ch) || unicode.IsDigit(s.ch) {
		s.next()
	}
	offset := s.offset
	if s.ch == rune(0) {
		offset++
	}
	lit := s.src[start:offset]
	return lit, token.Lookup(lit), s.file.Pos(start)
}

func (s *Scanner) scanNumber() (string, token.Token, token.Pos) {
	start := s.offset

	for unicode.IsDigit(s.ch) {
		s.next()
	}
	offset := s.offset
	if s.ch == rune(0) {
		offset++
	}
	return s.src[start:offset], token.INTEGER, s.file.Pos(start)
}

func (s *Scanner) skipComment() {
	for s.ch != '\n' && s.offset < len(s.src)-1 {
		s.next()
	}
}

func (s *Scanner) skipWhitespace() {
	for unicode.IsSpace(s.ch) {
		s.next()
	}
}
<commit_msg>fix newline bug in Scanner.next<commit_after>// Copyright (c) 2014, Rob Thornton
// All rights reserved.
// This source code is governed by a Simplied BSD-License. Please see the
// LICENSE included in this distribution for a copy of the full license
// or, if one is not included, you may also find a copy at
// http://opensource.org/licenses/BSD-2-Clause

package scan

import (
	"unicode"

	"github.com/rthornton128/calc/token"
)

type Scanner struct {
	ch      rune
	offset  int
	roffset int
	src     string
	file    *token.File
}

func (s *Scanner) Init(file *token.File, src string) {
	s.file = file
	s.offset, s.roffset = 0, 0
	s.src = src

	s.next()
}

func (s *Scanner) Scan() (lit string, tok token.Token, pos token.Pos) {
	s.skipWhitespace()

	if unicode.IsLetter(s.ch) {
		return s.scanIdentifier()
	}

	if unicode.IsDigit(s.ch) {
		return s.scanNumber()
	}

	ch := s.ch
	lit, pos = string(s.ch), s.file.Pos(s.offset)
	s.next()
	switch ch {
	case '(':
		tok = token.LPAREN
	case ')':
		tok = token.RPAREN
	case ',':
		tok = token.COMMA
	case '+':
		tok = token.ADD
	case '-':
		tok = token.SUB
	case '*':
		tok = token.MUL
	case '/':
		tok = token.QUO
	case '%':
		tok = token.REM
	case '<':
		if s.ch == '=' {
			tok = token.LTE
			s.next()
		} else {
			tok = token.LST
		}
	case '>':
		if s.ch == '=' {
			tok = token.GTE
			s.next()
		} else {
			tok = token.GTT
		}
	case '=':
		if s.ch == '=' {
			tok = token.EQL
			s.next()
		} else {
			tok = token.ASSIGN
		}
	case '!':
		if s.ch == '=' {
			tok = token.NEQ
			s.next()
		} else {
			tok = token.ILLEGAL
		}
	case '&':
		if s.ch == '&' {
			tok = token.AND
			s.next()
		} else {
			tok = token.ILLEGAL
		}
	case '|':
		if s.ch == '|' {
			tok = token.OR
			s.next()
		} else {
			tok = token.ILLEGAL
		}
	case ';':
		s.skipComment()
		s.next()
		return s.Scan()
	default:
		if s.offset >= len(s.src)-1 {
			tok = token.EOF
		} else {
			tok = token.ILLEGAL
		}
	}

	return
}

func (s *Scanner) next() {
	s.ch = rune(0)
	if s.roffset < len(s.src) {
		s.offset = s.roffset
		s.ch = rune(s.src[s.offset])
		if s.ch == '\n' || s.offset >= len(s.src)-1 {
			s.file.AddLine(token.Pos(s.offset))
		}
		s.roffset++
	}
}

func (s *Scanner) scanIdentifier() (string, token.Token, token.Pos) {
	start := s.offset

	for unicode.IsLetter(s.ch) || unicode.IsDigit(s.ch) {
		s.next()
	}
	offset := s.offset
	if s.ch == rune(0) {
		offset++
	}
	lit := s.src[start:offset]
	return lit, token.Lookup(lit), s.file.Pos(start)
}

func (s *Scanner) scanNumber() (string, token.Token, token.Pos) {
	start := s.offset

	for unicode.IsDigit(s.ch) {
		s.next()
	}
	offset := s.offset
	if s.ch == rune(0) {
		offset++
	}
	return s.src[start:offset], token.INTEGER, s.file.Pos(start)
}

func (s *Scanner) skipComment() {
	for s.ch != '\n' && s.offset < len(s.src)-1 {
		s.next()
	}
}

func (s *Scanner) skipWhitespace() {
	for unicode.IsSpace(s.ch) {
		s.next()
	}
}
<|endoftext|>


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

package featuretests_test

import (
	"fmt"
	"os"
	"testing"
	"time"

	"v.io/v23/naming"
	"v.io/v23/services/watch"
	"v.io/v23/syncbase"
	"v.io/v23/syncbase/nosql"
	constants "v.io/x/ref/services/syncbase/server/util"
	"v.io/x/ref/test/v23test"
)

const pingPongPairIterations = 500

// BenchmarkPingPongPair measures the round trip sync latency between a pair of
// Syncbase instances that Ping Pong data to each other over a syncgroup.
//
// This benchmark performs the following operations:
// - Create two syncbase instances and have them join the same syncgroup.
// - Each watches the other syncbase's "section" of the table.
// - A preliminary write by each syncbase ensures that clocks are synced.
// - During Ping Pong, each syncbase instance writes data to its own section of
//   the table. The other syncbase watches that section for writes. Once a write
//   is received, it does the same.
//
// After the benchmark completes, the "ns/op" value refers to the average time
// for |pingPongPairIterations| of Ping Pong roundtrips completed.
func BenchmarkPingPongPair(b *testing.B) {
	sh := v23test.NewShell(b, v23test.Opts{})
	defer sh.Cleanup()
	sh.StartRootMountTable()

	b.ResetTimer()
	b.StopTimer()

	for iter := 0; iter < b.N; iter++ {
		// Setup 2 Syncbases.
		sbs := setupSyncbases(b, sh, 2)

		// Syncbase s0 is the creator.
		sgName := naming.Join(sbs[0].sbName, constants.SyncbaseSuffix, "SG1")

		// TODO(alexfandrianto): Was unable to use the empty prefix ("tb:").
		// Observation: w0's watch isn't working with the empty prefix.
		// Possible Explanation: The empty prefix ACL receives an initial value from
		// the Table ACL. If this value is synced over from the opposing peer,
		// conflict resolution can mean that s0 loses the ability to watch.
		syncString := fmt.Sprintf("%s:p", testTable)
		ok(b, createSyncgroup(sbs[0].clientCtx, sbs[0].sbName, sgName, syncString, "", sbBlessings(sbs), nil))

		// Syncbase s1 will attempt to join the syncgroup.
		ok(b, joinSyncgroup(sbs[1].clientCtx, sbs[1].sbName, sgName))

		// Obtain the handles to the databases.
		db0, _ := getDbAndTable(sbs[0].sbName)
		db1, _ := getDbAndTable(sbs[1].sbName)

		// Set up the watch streams (watching the other syncbase's prefix).
		prefix0, prefix1 := "prefix0", "prefix1"
		w0, err := db0.Watch(sbs[0].clientCtx, testTable, prefix1, watch.ResumeMarker("now"))
		ok(b, err)
		w1, err := db1.Watch(sbs[1].clientCtx, testTable, prefix0, watch.ResumeMarker("now"))
		ok(b, err)

		// The join has succeeded, so make sure sync is initialized.
		// The strategy is: s0 sends to s1, and then s1 responds.
		sendInt32Sync(b, sbs[0], prefix0, w1)
		sendInt32Sync(b, sbs[1], prefix1, w0)

		// Sync is now active, so it is time to really start our watch streams.
		c0, c1 := make(chan int32), make(chan int32)
		go watchInt32s(b, w0, c0)
		go watchInt32s(b, w1, c1)

		// Perform and time |pingPongIterations| of ping pong between syncbases.
		b.StartTimer()
		lastTime := time.Now()
		var t0to1, t1to0 int64
		for i := 0; i < pingPongPairIterations; i++ {
			var delta int64
			value := int32(i)
			ok(b, writeInt32(sbs[0], prefix0, value))
			<-c1
			delta, lastTime = getDelta(lastTime)
			t0to1 += delta
			ok(b, writeInt32(sbs[1], prefix1, value))
			<-c0
			delta, lastTime = getDelta(lastTime)
			t1to0 += delta
		}
		b.StopTimer()

		// Clean up syncbases.
		for i, _ := range sbs {
			sbs[i].cleanup(os.Interrupt)
		}

		// Print out intermediate information.
		fmt.Printf("Iteration %d of %d\n", iter, b.N)
		fmt.Printf("Avg Time from 0 to 1: %d ns\n", t0to1/int64(pingPongPairIterations))
		fmt.Printf("Avg Time from 1 to 0: %d ns\n", t1to0/int64(pingPongPairIterations))
		fmt.Printf("Avg Time per iteration: %d ns\n", (t1to0+t0to1)/int64(pingPongPairIterations))
	}
}

// getDelta computes the delta between the last time and now.
// It returns both that delta and the new time.
func getDelta(lastTime time.Time) (delta int64, nextTime time.Time) {
	nextTime = time.Now()
	delta = nextTime.Sub(lastTime).Nanoseconds()
	return
}

// sendInt32Sync sends data from 1 syncbase to another.
// Be sure that the receiving watch stream sees the data and is still ok.
func sendInt32Sync(b *testing.B, ts *testSyncbase, senderPrefix string, w nosql.WatchStream) {
	ok(b, writeInt32(ts, senderPrefix, -1))
	if w.Advance() {
		w.Change() // grab the change, but ignore the value.
	}
	watchStreamOk(b, w)
}

// watchInt32s sends the value of each put through to the channel.
func watchInt32s(b *testing.B, w nosql.WatchStream, c chan int32) {
	var count int
	for count < pingPongPairIterations && w.Advance() {
		var t int32
		change := w.Change()
		if change.ChangeType == nosql.DeleteChange {
			b.Error("Received a delete change")
		}
		err := change.Value(&t)
		if err != nil {
			b.Error(err)
		}
		c <- t
		watchStreamOk(b, w)
		count++
	}

	w.Cancel() // The stream can be canceled since we've seen enough iterations.
}

// getDbAndTable obtains the database and table handles for a syncbase name.
func getDbAndTable(syncbaseName string) (d nosql.Database, tb nosql.Table) {
	a := syncbase.NewService(syncbaseName).App(testApp)
	d = a.NoSQLDatabase(testDb, nil)
	tb = d.Table(testTable)
	return
}

// writeInt32 writes keyValue into syncbase at keyPrefix/keyValue.
func writeInt32(ts *testSyncbase, keyPrefix string, keyValue int32) error {
	ctx := ts.clientCtx
	syncbaseName := ts.sbName
	_, tb := getDbAndTable(syncbaseName)

	key := fmt.Sprintf("%s/%d", keyPrefix, keyValue)

	if err := tb.Put(ctx, key, keyValue); err != nil {
		return fmt.Errorf("tb.Put() failed: %v", err)
	}
	return nil
}

// watchStreamOk emits an error if the watch stream has an error.
func watchStreamOk(b *testing.B, w nosql.WatchStream) {
	if w.Err() != nil {
		b.Errorf("stream error: %v", w.Err())
	}
}
<commit_msg>syncbase/featuretests: Avoid fmt.Print in the benchmark.<commit_after>// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package featuretests_test

import (
	"fmt"
	"os"
	"testing"
	"time"

	"v.io/v23/naming"
	"v.io/v23/services/watch"
	"v.io/v23/syncbase"
	"v.io/v23/syncbase/nosql"
	constants "v.io/x/ref/services/syncbase/server/util"
	"v.io/x/ref/test/v23test"
)

const pingPongPairIterations = 500

// BenchmarkPingPongPair measures the round trip sync latency between a pair of
// Syncbase instances that Ping Pong data to each other over a syncgroup.
//
// This benchmark performs the following operations:
// - Create two syncbase instances and have them join the same syncgroup.
// - Each watches the other syncbase's "section" of the table.
// - A preliminary write by each syncbase ensures that clocks are synced.
// - During Ping Pong, each syncbase instance writes data to its own section of
//   the table. The other syncbase watches that section for writes. Once a write
//   is received, it does the same.
//
// After the benchmark completes, the "ns/op" value refers to the average time
// for |pingPongPairIterations| of Ping Pong roundtrips completed.
func BenchmarkPingPongPair(b *testing.B) {
	sh := v23test.NewShell(b, v23test.Opts{})
	defer sh.Cleanup()
	sh.StartRootMountTable()

	b.ResetTimer()
	b.StopTimer()

	for iter := 0; iter < b.N; iter++ {
		// Setup 2 Syncbases.
		sbs := setupSyncbases(b, sh, 2)

		// Syncbase s0 is the creator.
		sgName := naming.Join(sbs[0].sbName, constants.SyncbaseSuffix, "SG1")

		// TODO(alexfandrianto): Was unable to use the empty prefix ("tb:").
		// Observation: w0's watch isn't working with the empty prefix.
		// Possible Explanation: The empty prefix ACL receives an initial value from
		// the Table ACL. If this value is synced over from the opposing peer,
		// conflict resolution can mean that s0 loses the ability to watch.
		syncString := fmt.Sprintf("%s:p", testTable)
		ok(b, createSyncgroup(sbs[0].clientCtx, sbs[0].sbName, sgName, syncString, "", sbBlessings(sbs), nil))

		// Syncbase s1 will attempt to join the syncgroup.
		ok(b, joinSyncgroup(sbs[1].clientCtx, sbs[1].sbName, sgName))

		// Obtain the handles to the databases.
		db0, _ := getDbAndTable(sbs[0].sbName)
		db1, _ := getDbAndTable(sbs[1].sbName)

		// Set up the watch streams (watching the other syncbase's prefix).
		prefix0, prefix1 := "prefix0", "prefix1"
		w0, err := db0.Watch(sbs[0].clientCtx, testTable, prefix1, watch.ResumeMarker("now"))
		ok(b, err)
		w1, err := db1.Watch(sbs[1].clientCtx, testTable, prefix0, watch.ResumeMarker("now"))
		ok(b, err)

		// The join has succeeded, so make sure sync is initialized.
		// The strategy is: s0 sends to s1, and then s1 responds.
		sendInt32Sync(b, sbs[0], prefix0, w1)
		sendInt32Sync(b, sbs[1], prefix1, w0)

		// Sync is now active, so it is time to really start our watch streams.
		c0, c1 := make(chan int32), make(chan int32)
		go watchInt32s(b, w0, c0)
		go watchInt32s(b, w1, c1)

		// Perform and time |pingPongIterations| of ping pong between syncbases.
		b.StartTimer()
		lastTime := time.Now()
		var t0to1, t1to0 int64
		for i := 0; i < pingPongPairIterations; i++ {
			var delta int64
			value := int32(i)
			ok(b, writeInt32(sbs[0], prefix0, value))
			<-c1
			delta, lastTime = getDelta(lastTime)
			t0to1 += delta
			ok(b, writeInt32(sbs[1], prefix1, value))
			<-c0
			delta, lastTime = getDelta(lastTime)
			t1to0 += delta
		}
		b.StopTimer()

		// Clean up syncbases.
		for i, _ := range sbs {
			sbs[i].cleanup(os.Interrupt)
		}

		// Log intermediate information
		b.Logf("Iteration %d of %d\n", iter, b.N)
		b.Logf("Avg Time from 0 to 1: %d ns\n", t0to1/int64(pingPongPairIterations))
		b.Logf("Avg Time from 1 to 0: %d ns\n", t1to0/int64(pingPongPairIterations))
		b.Logf("Avg Time per iteration: %d ns\n", (t1to0+t0to1)/int64(pingPongPairIterations))
	}
}

// getDelta computes the delta between the last time and now.
// It returns both that delta and the new time.
func getDelta(lastTime time.Time) (delta int64, nextTime time.Time) {
	nextTime = time.Now()
	delta = nextTime.Sub(lastTime).Nanoseconds()
	return
}

// sendInt32Sync sends data from 1 syncbase to another.
// Be sure that the receiving watch stream sees the data and is still ok.
func sendInt32Sync(b *testing.B, ts *testSyncbase, senderPrefix string, w nosql.WatchStream) {
	ok(b, writeInt32(ts, senderPrefix, -1))
	if w.Advance() {
		w.Change() // grab the change, but ignore the value.
	}
	watchStreamOk(b, w)
}

// watchInt32s sends the value of each put through to the channel.
func watchInt32s(b *testing.B, w nosql.WatchStream, c chan int32) {
	var count int
	for count < pingPongPairIterations && w.Advance() {
		var t int32
		change := w.Change()
		if change.ChangeType == nosql.DeleteChange {
			b.Error("Received a delete change")
		}
		err := change.Value(&t)
		if err != nil {
			b.Error(err)
		}
		c <- t
		watchStreamOk(b, w)
		count++
	}

	w.Cancel() // The stream can be canceled since we've seen enough iterations.
}

// getDbAndTable obtains the database and table handles for a syncbase name.
func getDbAndTable(syncbaseName string) (d nosql.Database, tb nosql.Table) {
	a := syncbase.NewService(syncbaseName).App(testApp)
	d = a.NoSQLDatabase(testDb, nil)
	tb = d.Table(testTable)
	return
}

// writeInt32 writes keyValue into syncbase at keyPrefix/keyValue.
func writeInt32(ts *testSyncbase, keyPrefix string, keyValue int32) error {
	ctx := ts.clientCtx
	syncbaseName := ts.sbName
	_, tb := getDbAndTable(syncbaseName)

	key := fmt.Sprintf("%s/%d", keyPrefix, keyValue)

	if err := tb.Put(ctx, key, keyValue); err != nil {
		return fmt.Errorf("tb.Put() failed: %v", err)
	}
	return nil
}

// watchStreamOk emits an error if the watch stream has an error.
func watchStreamOk(b *testing.B, w nosql.WatchStream) {
	if w.Err() != nil {
		b.Errorf("stream error: %v", w.Err())
	}
}
<|endoftext|>


<commit_before><commit_msg>add java time<commit_after><|endoftext|>


<commit_before><commit_msg>Feature interfaces<commit_after><|endoftext|>


<commit_before>package configfile

import (
	"fmt"
	"testing"
	"time"

	"github.com/rfjakob/gocryptfs/internal/tlog"
)

func TestLoadV1(t *testing.T) {
	_, _, err := LoadConfFile("config_test/v1.conf", "test")
	if err == nil {
		t.Errorf("Outdated v1 config file must fail to load but it didn't")
	} else if testing.Verbose() {
		fmt.Println(err)
	}
}

// Load a known-good config file and verify that it takes at least 100ms
// (brute-force protection)
func TestLoadV2(t *testing.T) {
	t1 := time.Now()

	_, _, err := LoadConfFile("config_test/v2.conf", "test")
	if err != nil {
		t.Errorf("Could not load v2 config file: %v", err)
	}

	elapsed := time.Since(t1)
	if elapsed < 100*time.Millisecond {
		t.Errorf("scrypt calculation runs too fast: %d ms", elapsed/time.Millisecond)
	}
}

func TestLoadV2PwdError(t *testing.T) {
	if !testing.Verbose() {
		tlog.Warn.Enabled = false
	}
	_, _, err := LoadConfFile("config_test/v2.conf", "wrongpassword")
	if err == nil {
		t.Errorf("Loading with wrong password must fail but it didn't")
	}
}

func TestLoadV2Feature(t *testing.T) {
	_, _, err := LoadConfFile("config_test/PlaintextNames.conf", "test")
	if err != nil {
		t.Errorf("Could not load v2 PlaintextNames config file: %v", err)
	}
}

func TestLoadV2StrangeFeature(t *testing.T) {
	_, _, err := LoadConfFile("config_test/StrangeFeature.conf", "test")
	if err == nil {
		t.Errorf("Loading unknown feature must fail but it didn't")
	} else if testing.Verbose() {
		fmt.Println(err)
	}
}

func TestCreateConfFile(t *testing.T) {
	err := CreateConfFile("config_test/tmp.conf", "test", false, 10, "test", false)
	if err != nil {
		t.Fatal(err)
	}
	_, c, err := LoadConfFile("config_test/tmp.conf", "test")
	if err != nil {
		t.Fatal(err)
	}
	// Raw64 is set by default since gocryptfs v1.3
	/*
		if !c.IsFeatureFlagSet(FlagRaw64) {
			t.Error("FlagRaw64 flag should be set but is not")
		}
	*/
}

func TestCreateConfFileAESSIV(t *testing.T) {
	err := CreateConfFile("config_test/tmp.conf", "test", false, 10, "test", true)
	if err != nil {
		t.Fatal(err)
	}
	_, c, err := LoadConfFile("config_test/tmp.conf", "test")
	if err != nil {
		t.Fatal(err)
	}
	if !c.IsFeatureFlagSet(FlagAESSIV) {
		t.Error("AESSIV flag should be set but is not")
	}
}

func TestIsFeatureFlagKnown(t *testing.T) {
	// Test a few hardcoded values
	testKnownFlags := []string{"DirIV", "PlaintextNames", "EMENames", "GCMIV128", "LongNames", "AESSIV"}
	// And also everything in knownFlags (yes, it is likely that we end up with
	// some duplicates. Does not matter.)
	for _, f := range knownFlags {
		testKnownFlags = append(testKnownFlags, f)
	}

	var cf ConfFile
	for _, f := range testKnownFlags {
		if !cf.isFeatureFlagKnown(f) {
			t.Errorf("flag %q should be known", f)
		}
	}

	f := "StrangeFeatureFlag"
	if cf.isFeatureFlagKnown(f) {
		t.Errorf("flag %q should be NOT known", f)
	}
}
<commit_msg>tests: configfile: fix spurious test failure II<commit_after>package configfile

import (
	"fmt"
	"testing"
	"time"

	"github.com/rfjakob/gocryptfs/internal/tlog"
)

func TestLoadV1(t *testing.T) {
	_, _, err := LoadConfFile("config_test/v1.conf", "test")
	if err == nil {
		t.Errorf("Outdated v1 config file must fail to load but it didn't")
	} else if testing.Verbose() {
		fmt.Println(err)
	}
}

// Load a known-good config file and verify that it takes at least 100ms
// (brute-force protection)
func TestLoadV2(t *testing.T) {
	t1 := time.Now()

	_, _, err := LoadConfFile("config_test/v2.conf", "test")
	if err != nil {
		t.Errorf("Could not load v2 config file: %v", err)
	}

	elapsed := time.Since(t1)
	if elapsed < 100*time.Millisecond {
		t.Errorf("scrypt calculation runs too fast: %d ms", elapsed/time.Millisecond)
	}
}

func TestLoadV2PwdError(t *testing.T) {
	if !testing.Verbose() {
		tlog.Warn.Enabled = false
	}
	_, _, err := LoadConfFile("config_test/v2.conf", "wrongpassword")
	if err == nil {
		t.Errorf("Loading with wrong password must fail but it didn't")
	}
}

func TestLoadV2Feature(t *testing.T) {
	_, _, err := LoadConfFile("config_test/PlaintextNames.conf", "test")
	if err != nil {
		t.Errorf("Could not load v2 PlaintextNames config file: %v", err)
	}
}

func TestLoadV2StrangeFeature(t *testing.T) {
	_, _, err := LoadConfFile("config_test/StrangeFeature.conf", "test")
	if err == nil {
		t.Errorf("Loading unknown feature must fail but it didn't")
	} else if testing.Verbose() {
		fmt.Println(err)
	}
}

func TestCreateConfFile(t *testing.T) {
	err := CreateConfFile("config_test/tmp.conf", "test", false, 10, "test", false)
	if err != nil {
		t.Fatal(err)
	}
	_, _, err = LoadConfFile("config_test/tmp.conf", "test")
	if err != nil {
		t.Fatal(err)
	}
	// Raw64 is set by default since gocryptfs v1.3
	/*
		if !c.IsFeatureFlagSet(FlagRaw64) {
			t.Error("FlagRaw64 flag should be set but is not")
		}
	*/
}

func TestCreateConfFileAESSIV(t *testing.T) {
	err := CreateConfFile("config_test/tmp.conf", "test", false, 10, "test", true)
	if err != nil {
		t.Fatal(err)
	}
	_, c, err := LoadConfFile("config_test/tmp.conf", "test")
	if err != nil {
		t.Fatal(err)
	}
	if !c.IsFeatureFlagSet(FlagAESSIV) {
		t.Error("AESSIV flag should be set but is not")
	}
}

func TestIsFeatureFlagKnown(t *testing.T) {
	// Test a few hardcoded values
	testKnownFlags := []string{"DirIV", "PlaintextNames", "EMENames", "GCMIV128", "LongNames", "AESSIV"}
	// And also everything in knownFlags (yes, it is likely that we end up with
	// some duplicates. Does not matter.)
	for _, f := range knownFlags {
		testKnownFlags = append(testKnownFlags, f)
	}

	var cf ConfFile
	for _, f := range testKnownFlags {
		if !cf.isFeatureFlagKnown(f) {
			t.Errorf("flag %q should be known", f)
		}
	}

	f := "StrangeFeatureFlag"
	if cf.isFeatureFlagKnown(f) {
		t.Errorf("flag %q should be NOT known", f)
	}
}
<|endoftext|>


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

package dochtml

import (
	"html/template"
	"reflect"

	"golang.org/x/discovery/internal/fetch/dochtml/internal/render"
)

// htmlPackage is the template used to render documentation HTML.
// TODO(b/142795082): finalize URL scheme and design for notes, then factor out
// inline CSS style.
var htmlPackage = template.Must(template.New("package").Funcs(
	map[string]interface{}{
		"ternary": func(q, a, b interface{}) interface{} {
			v := reflect.ValueOf(q)
			vz := reflect.New(v.Type()).Elem()
			if reflect.DeepEqual(v.Interface(), vz.Interface()) {
				return b
			}
			return a
		},
		"render_synopsis": (*render.Renderer)(nil).Synopsis,
		"render_doc":      (*render.Renderer)(nil).DocHTML,
		"render_decl":     (*render.Renderer)(nil).DeclHTML,
		"render_code":     (*render.Renderer)(nil).CodeHTML,
		"source_link":     func() string { return "" },
	},
).Parse(`{{- "" -}}
{{- if or .Doc .Consts .Vars .Funcs .Types .Examples.List -}}

{{"\n" -}}
	{{- if or .Doc (index .Examples.Map "") -}}

Overview
{{"\n" -}}
	{{- end -}}
	{{- if or .Consts .Vars .Funcs .Types -}}

Index
{{"\n" -}}
	{{- end -}}
	{{- if .Examples.List -}}

Examples
{{"\n" -}}
	{{- end -}}

{{"\n" -}}
{{- end -}}

{{- if or .Doc (index .Examples.Map "") -}}

Overview ¶
{{"\n\n" -}}
		{{render_doc .Doc}}{{"\n" -}}
		{{- template "example" (index .Examples.Map "") -}}

{{- end -}}

{{- if or .Consts .Vars .Funcs .Types -}}

Index ¶
{{"\n\n" -}}

{{"\n" -}}
			{{- if .Consts -}}
Constants
{{"\n"}}{{- end -}}
			{{- if .Vars -}}
Variables
{{"\n"}}{{- end -}}

			{{- range .Funcs -}}

				{{render_synopsis .Decl}}

{{"\n"}}
			{{- end -}}

			{{- range .Types -}}
				{{- $tname := .Name -}}

type {{$tname}}
{{"\n"}}
				{{- with .Funcs -}}

{{"\n" -}}
					{{range .}}
{{render_synopsis .Decl}}
{{"\n"}}{{end}}

{{"\n" -}}
				{{- end -}}
				{{- with .Methods -}}

{{"\n" -}}
					{{range .}}
{{render_synopsis .Decl}}
{{"\n"}}{{end}}

{{"\n" -}}
				{{- end -}}
			{{- end -}}

			{{- range $marker, $item := .Notes -}}

{{$marker}}s

			{{- end -}}

{{"\n" -}}

	{{- if .Examples.List -}}

Examples ¶
{{"\n" -}}

{{"\n" -}}
			{{- range .Examples.List -}}

{{or .ParentID "Package"}}{{with .Suffix}} ({{.}}){{end}}
{{"\n" -}}
			{{- end -}}

{{"\n" -}}

	{{- end -}}

	{{- if .Consts -}}

Constants ¶
{{"\n"}}
		{{- range .Consts -}}
			{{- $out := render_decl .Doc .Decl -}}
			{{- $out.Decl -}}
			{{- $out.Doc -}}
			{{"\n"}}
		{{- end -}}

	{{- end -}}

	{{- if .Vars -}}

Variables ¶
{{"\n"}}
		{{- range .Vars -}}
			{{- $out := render_decl .Doc .Decl -}}
			{{- $out.Decl -}}
			{{- $out.Doc -}}
			{{"\n"}}
		{{- end -}}

	{{- end -}}

	{{- if .Funcs -}}

		{{- range .Funcs -}}

func {{source_link .Name .Decl}} ¶
{{"\n"}}
			{{- $out := render_decl .Doc .Decl -}}
			{{- $out.Decl -}}
			{{- $out.Doc -}}
			{{"\n"}}
			{{- template "example" (index $.Examples.Map .Name) -}}

		{{- end -}}

	{{- end -}}

	{{- if .Types -}}

		{{- range .Types -}}

			{{- $tname := .Name -}}

type {{source_link .Name .Decl}} ¶
{{"\n"}}
			{{- $out := render_decl .Doc .Decl -}}
			{{- $out.Decl -}}
			{{- $out.Doc -}}
			{{"\n"}}
			{{- template "example" (index $.Examples.Map .Name) -}}

			{{- range .Consts -}}

				{{- $out := render_decl .Doc .Decl -}}
				{{- $out.Decl -}}
				{{- $out.Doc -}}
				{{"\n"}}

			{{- end -}}

			{{- range .Vars -}}

				{{- $out := render_decl .Doc .Decl -}}
				{{- $out.Decl -}}
				{{- $out.Doc -}}
				{{"\n"}}

			{{- end -}}

			{{- range .Funcs -}}

func {{source_link .Name .Decl}} ¶
{{"\n"}}
				{{- $out := render_decl .Doc .Decl -}}
				{{- $out.Decl -}}
				{{- $out.Doc -}}
				{{"\n"}}
				{{- template "example" (index $.Examples.Map .Name) -}}

			{{- end -}}

			{{- range .Methods -}}

				{{- $name := (printf "%s.%s" $tname .Name) -}}

func ({{.Recv}}) {{source_link .Name .Decl}} ¶
{{"\n"}}
				{{- $out := render_decl .Doc .Decl -}}
				{{- $out.Decl -}}
				{{- $out.Doc -}}
				{{"\n"}}
				{{- template "example" (index $.Examples.Map $name) -}}

			{{- end -}}

		{{- end -}}

	{{- end -}}
{{- end -}}

{{- if .Notes -}}

	{{- range $marker, $content := .Notes -}}

{{$marker}}s ¶

{{"\n" -}}
		{{- range $v := $content -}}

{{render_doc $v.Body}}

		{{- end -}}

{{"\n" -}}

	{{- end -}}

{{- end -}}

{{- define "example" -}}
	{{- range . -}}
	{{"\n" -}}
		Example{{with .Suffix}} ({{.}}){{end}} ¶{{"\n" -}}

{{"\n" -}}
			{{- if .Doc -}}{{render_doc .Doc}}{{"\n" -}}{{- end -}}

Code:
{{"\n" -}}
			{{render_code .Code}}{{"\n" -}}
			{{- if (or .Output .EmptyOutput) -}}

{{ternary .Unordered "Unordered output:" "Output:"}}
{{"\n" -}}

{{"\n"}}{{.Output}}
{{"\n" -}}
			{{- end -}}

{{"\n" -}}
	{{"\n" -}}
	{{"\n"}}
	{{- end -}}
{{- end -}}
`))
<commit_msg>internal/fetch/dochtml: show Examples section when no exported identifiers<commit_after>// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package dochtml

import (
	"html/template"
	"reflect"

	"golang.org/x/discovery/internal/fetch/dochtml/internal/render"
)

// htmlPackage is the template used to render documentation HTML.
// TODO(b/142795082): finalize URL scheme and design for notes, then factor out
// inline CSS style.
var htmlPackage = template.Must(template.New("package").Funcs(
	map[string]interface{}{
		"ternary": func(q, a, b interface{}) interface{} {
			v := reflect.ValueOf(q)
			vz := reflect.New(v.Type()).Elem()
			if reflect.DeepEqual(v.Interface(), vz.Interface()) {
				return b
			}
			return a
		},
		"render_synopsis": (*render.Renderer)(nil).Synopsis,
		"render_doc":      (*render.Renderer)(nil).DocHTML,
		"render_decl":     (*render.Renderer)(nil).DeclHTML,
		"render_code":     (*render.Renderer)(nil).CodeHTML,
		"source_link":     func() string { return "" },
	},
).Parse(`{{- "" -}}
{{- if or .Doc .Consts .Vars .Funcs .Types .Examples.List -}}

{{"\n" -}}
	{{- if or .Doc (index .Examples.Map "") -}}

Overview
{{"\n" -}}
	{{- end -}}
	{{- if or .Consts .Vars .Funcs .Types .Examples.List -}}

Index
{{"\n" -}}
	{{- end -}}
	{{- if .Examples.List -}}

Examples
{{"\n" -}}
	{{- end -}}

{{"\n" -}}
{{- end -}}

{{- if or .Doc (index .Examples.Map "") -}}

Overview ¶
{{"\n\n" -}}
		{{render_doc .Doc}}{{"\n" -}}
		{{- template "example" (index .Examples.Map "") -}}

{{- end -}}

{{- if or .Consts .Vars .Funcs .Types .Examples.List -}}

Index ¶
{{"\n\n" -}}

{{"\n" -}}
			{{- if .Consts -}}
Constants
{{"\n"}}{{- end -}}
			{{- if .Vars -}}
Variables
{{"\n"}}{{- end -}}

			{{- range .Funcs -}}

				{{render_synopsis .Decl}}

{{"\n"}}
			{{- end -}}

			{{- range .Types -}}
				{{- $tname := .Name -}}

type {{$tname}}
{{"\n"}}
				{{- with .Funcs -}}

{{"\n" -}}
					{{range .}}
{{render_synopsis .Decl}}
{{"\n"}}{{end}}

{{"\n" -}}
				{{- end -}}
				{{- with .Methods -}}

{{"\n" -}}
					{{range .}}
{{render_synopsis .Decl}}
{{"\n"}}{{end}}

{{"\n" -}}
				{{- end -}}
			{{- end -}}

			{{- range $marker, $item := .Notes -}}

{{$marker}}s

			{{- end -}}

{{"\n" -}}

	{{- if .Examples.List -}}

Examples ¶
{{"\n" -}}

{{"\n" -}}
			{{- range .Examples.List -}}

{{or .ParentID "Package"}}{{with .Suffix}} ({{.}}){{end}}
{{"\n" -}}
			{{- end -}}

{{"\n" -}}

	{{- end -}}

	{{- if .Consts -}}

Constants ¶
{{"\n"}}
		{{- range .Consts -}}
			{{- $out := render_decl .Doc .Decl -}}
			{{- $out.Decl -}}
			{{- $out.Doc -}}
			{{"\n"}}
		{{- end -}}

	{{- end -}}

	{{- if .Vars -}}

Variables ¶
{{"\n"}}
		{{- range .Vars -}}
			{{- $out := render_decl .Doc .Decl -}}
			{{- $out.Decl -}}
			{{- $out.Doc -}}
			{{"\n"}}
		{{- end -}}

	{{- end -}}

	{{- if .Funcs -}}

		{{- range .Funcs -}}

func {{source_link .Name .Decl}} ¶
{{"\n"}}
			{{- $out := render_decl .Doc .Decl -}}
			{{- $out.Decl -}}
			{{- $out.Doc -}}
			{{"\n"}}
			{{- template "example" (index $.Examples.Map .Name) -}}

		{{- end -}}

	{{- end -}}

	{{- if .Types -}}

		{{- range .Types -}}

			{{- $tname := .Name -}}

type {{source_link .Name .Decl}} ¶
{{"\n"}}
			{{- $out := render_decl .Doc .Decl -}}
			{{- $out.Decl -}}
			{{- $out.Doc -}}
			{{"\n"}}
			{{- template "example" (index $.Examples.Map .Name) -}}

			{{- range .Consts -}}

				{{- $out := render_decl .Doc .Decl -}}
				{{- $out.Decl -}}
				{{- $out.Doc -}}
				{{"\n"}}

			{{- end -}}

			{{- range .Vars -}}

				{{- $out := render_decl .Doc .Decl -}}
				{{- $out.Decl -}}
				{{- $out.Doc -}}
				{{"\n"}}

			{{- end -}}

			{{- range .Funcs -}}

func {{source_link .Name .Decl}} ¶
{{"\n"}}
				{{- $out := render_decl .Doc .Decl -}}
				{{- $out.Decl -}}
				{{- $out.Doc -}}
				{{"\n"}}
				{{- template "example" (index $.Examples.Map .Name) -}}

			{{- end -}}

			{{- range .Methods -}}

				{{- $name := (printf "%s.%s" $tname .Name) -}}

func ({{.Recv}}) {{source_link .Name .Decl}} ¶
{{"\n"}}
				{{- $out := render_decl .Doc .Decl -}}
				{{- $out.Decl -}}
				{{- $out.Doc -}}
				{{"\n"}}
				{{- template "example" (index $.Examples.Map $name) -}}

			{{- end -}}

		{{- end -}}

	{{- end -}}
{{- end -}}

{{- if .Notes -}}

	{{- range $marker, $content := .Notes -}}

{{$marker}}s ¶

{{"\n" -}}
		{{- range $v := $content -}}

{{render_doc $v.Body}}

		{{- end -}}

{{"\n" -}}

	{{- end -}}

{{- end -}}

{{- define "example" -}}
	{{- range . -}}
	{{"\n" -}}
		Example{{with .Suffix}} ({{.}}){{end}} ¶{{"\n" -}}

{{"\n" -}}
			{{- if .Doc -}}{{render_doc .Doc}}{{"\n" -}}{{- end -}}

Code:
{{"\n" -}}
			{{render_code .Code}}{{"\n" -}}
			{{- if (or .Output .EmptyOutput) -}}

{{ternary .Unordered "Unordered output:" "Output:"}}
{{"\n" -}}

{{"\n"}}{{.Output}}
{{"\n" -}}
			{{- end -}}

{{"\n" -}}
	{{"\n" -}}
	{{"\n"}}
	{{- end -}}
{{- end -}}
`))
<|endoftext|>


<commit_before>package read

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"runtime"

	"github.com/go-task/task/v2/internal/taskfile"

	"gopkg.in/yaml.v2"
)

// ErrIncludedTaskfilesCantHaveIncludes is returned when a included Taskfile contains includes
var ErrIncludedTaskfilesCantHaveIncludes = errors.New("task: Included Taskfiles can't have includes. Please, move the include to the main Taskfile")

// Taskfile reads a Taskfile for a given directory
func Taskfile(dir string) (*taskfile.Taskfile, error) {
	path := filepath.Join(dir, "Taskfile.yml")
	if _, err := os.Stat(path); err != nil {
		return nil, fmt.Errorf(`No Taskfile.yml found. Use "task --init" to create a new one`)
	}
	t, err := readTaskfile(path)
	if err != nil {
		return nil, err
	}

	for namespace, path := range t.Includes {
		path = filepath.Join(dir, path)
		info, err := os.Stat(path)
		if err != nil {
			return nil, err
		}
		if info.IsDir() {
			path = filepath.Join(path, "Taskfile.yml")
		}
		includedTaskfile, err := readTaskfile(path)
		if err != nil {
			return nil, err
		}
		if len(includedTaskfile.Includes) > 0 {
			return nil, ErrIncludedTaskfilesCantHaveIncludes
		}
		if err = taskfile.Merge(t, includedTaskfile, namespace); err != nil {
			return nil, err
		}
	}

	path = filepath.Join(dir, fmt.Sprintf("Taskfile_%s.yml", runtime.GOOS))
	if _, err = os.Stat(path); err == nil {
		osTaskfile, err := readTaskfile(path)
		if err != nil {
			return nil, err
		}
		if err = taskfile.Merge(t, osTaskfile); err != nil {
			return nil, err
		}
	}

	for name, task := range t.Tasks {
		task.Task = name
	}

	return t, nil
}

func readTaskfile(file string) (*taskfile.Taskfile, error) {
	f, err := os.Open(file)
	if err != nil {
		return nil, err
	}
	var t taskfile.Taskfile
	return &t, yaml.NewDecoder(f).Decode(&t)
}
<commit_msg>taskfile: return defined error when taskfile.yml is not found<commit_after>package read

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"runtime"

	"github.com/go-task/task/v2/internal/taskfile"

	"gopkg.in/yaml.v2"
)

var (
	// ErrIncludedTaskfilesCantHaveIncludes is returned when a included Taskfile contains includes
	ErrIncludedTaskfilesCantHaveIncludes = errors.New("task: Included Taskfiles can't have includes. Please, move the include to the main Taskfile")

	// ErrNoTaskfileFound is returned when Taskfile.yml is not found
	ErrNoTaskfileFound = errors.New(`no Taskfile.yml found. Use "task --init" to create a new one`)
)

// Taskfile reads a Taskfile for a given directory
func Taskfile(dir string) (*taskfile.Taskfile, error) {
	path := filepath.Join(dir, "Taskfile.yml")
	if _, err := os.Stat(path); err != nil {
		return nil, ErrNoTaskfileFound
	}
	t, err := readTaskfile(path)
	if err != nil {
		return nil, err
	}

	for namespace, path := range t.Includes {
		path = filepath.Join(dir, path)
		info, err := os.Stat(path)
		if err != nil {
			return nil, err
		}
		if info.IsDir() {
			path = filepath.Join(path, "Taskfile.yml")
		}
		includedTaskfile, err := readTaskfile(path)
		if err != nil {
			return nil, err
		}
		if len(includedTaskfile.Includes) > 0 {
			return nil, ErrIncludedTaskfilesCantHaveIncludes
		}
		if err = taskfile.Merge(t, includedTaskfile, namespace); err != nil {
			return nil, err
		}
	}

	path = filepath.Join(dir, fmt.Sprintf("Taskfile_%s.yml", runtime.GOOS))
	if _, err = os.Stat(path); err == nil {
		osTaskfile, err := readTaskfile(path)
		if err != nil {
			return nil, err
		}
		if err = taskfile.Merge(t, osTaskfile); err != nil {
			return nil, err
		}
	}

	for name, task := range t.Tasks {
		task.Task = name
	}

	return t, nil
}

func readTaskfile(file string) (*taskfile.Taskfile, error) {
	f, err := os.Open(file)
	if err != nil {
		return nil, err
	}
	var t taskfile.Taskfile
	return &t, yaml.NewDecoder(f).Decode(&t)
}
<|endoftext|>