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

// +build example jsgo

package main

import (
	"image/color"
	"log"
	"math/rand"
	"time"

	"golang.org/x/image/font"
	"golang.org/x/image/font/opentype"

	"github.com/hajimehoshi/ebiten"
	"github.com/hajimehoshi/ebiten/ebitenutil"
	"github.com/hajimehoshi/ebiten/examples/resources/fonts"
	"github.com/hajimehoshi/ebiten/text"
)

const (
	screenWidth  = 640
	screenHeight = 480
)

const sampleText = `The quick brown fox jumps
over the lazy dog.`

var (
	mplusNormalFont font.Face
	mplusBigFont    font.Face
)

func init() {
	tt, err := opentype.Parse(fonts.MPlus1pRegular_ttf)
	if err != nil {
		log.Fatal(err)
	}

	const dpi = 72
	mplusNormalFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
		Size:    24,
		DPI:     dpi,
		Hinting: font.HintingFull,
	})
	if err != nil {
		log.Fatal(err)
	}
	mplusBigFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
		Size:    32,
		DPI:     dpi,
		Hinting: font.HintingFull,
	})
	if err != nil {
		log.Fatal(err)
	}
}

func init() {
	rand.Seed(time.Now().UnixNano())
}

type Game struct {
	counter        int
	kanjiText      []rune
	kanjiTextColor color.RGBA
}

func (g *Game) Update(screen *ebiten.Image) error {
	return nil
}

func (g *Game) Draw(screen *ebiten.Image) {
	gray := color.RGBA{0x80, 0x80, 0x80, 0xff}

	{
		const x, y = 20, 40
		b := text.BoundString(mplusNormalFont, sampleText)
		ebitenutil.DrawRect(screen, float64(b.Min.X+x), float64(b.Min.Y+y), float64(b.Dx()), float64(b.Dy()), gray)
		text.Draw(screen, sampleText, mplusNormalFont, x, y, color.White)
	}
	{
		const x, y = 20, 140
		b := text.BoundString(mplusBigFont, sampleText)
		ebitenutil.DrawRect(screen, float64(b.Min.X+x), float64(b.Min.Y+y), float64(b.Dx()), float64(b.Dy()), gray)
		text.Draw(screen, sampleText, mplusBigFont, x, y, color.White)
	}
}

func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
	return screenWidth, screenHeight
}

func main() {
	ebiten.SetWindowSize(screenWidth, screenHeight)
	ebiten.SetWindowTitle("Text (Ebiten Demo)")
	if err := ebiten.RunGame(&Game{}); err != nil {
		log.Fatal(err)
	}
}
<commit_msg>Revert "examples/text: Use golang.org/x/image/font/opentype"<commit_after>// Copyright 2020 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// +build example jsgo

package main

import (
	"image/color"
	"log"
	"math/rand"
	"time"

	"github.com/golang/freetype/truetype"
	"golang.org/x/image/font"

	"github.com/hajimehoshi/ebiten"
	"github.com/hajimehoshi/ebiten/ebitenutil"
	"github.com/hajimehoshi/ebiten/examples/resources/fonts"
	"github.com/hajimehoshi/ebiten/text"
)

const (
	screenWidth  = 640
	screenHeight = 480
)

const sampleText = `The quick brown fox jumps
over the lazy dog.`

var (
	mplusNormalFont font.Face
	mplusBigFont    font.Face
)

func init() {
	tt, err := truetype.Parse(fonts.MPlus1pRegular_ttf)
	if err != nil {
		log.Fatal(err)
	}

	const dpi = 72
	mplusNormalFont = truetype.NewFace(tt, &truetype.Options{
		Size:    24,
		DPI:     dpi,
		Hinting: font.HintingFull,
	})
	mplusBigFont = truetype.NewFace(tt, &truetype.Options{
		Size:    32,
		DPI:     dpi,
		Hinting: font.HintingFull,
	})
}

func init() {
	rand.Seed(time.Now().UnixNano())
}

type Game struct {
	counter        int
	kanjiText      []rune
	kanjiTextColor color.RGBA
}

func (g *Game) Update(screen *ebiten.Image) error {
	return nil
}

func (g *Game) Draw(screen *ebiten.Image) {
	gray := color.RGBA{0x80, 0x80, 0x80, 0xff}

	{
		const x, y = 20, 40
		b := text.BoundString(mplusNormalFont, sampleText)
		ebitenutil.DrawRect(screen, float64(b.Min.X+x), float64(b.Min.Y+y), float64(b.Dx()), float64(b.Dy()), gray)
		text.Draw(screen, sampleText, mplusNormalFont, x, y, color.White)
	}
	{
		const x, y = 20, 140
		b := text.BoundString(mplusBigFont, sampleText)
		ebitenutil.DrawRect(screen, float64(b.Min.X+x), float64(b.Min.Y+y), float64(b.Dx()), float64(b.Dy()), gray)
		text.Draw(screen, sampleText, mplusBigFont, x, y, color.White)
	}
}

func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
	return screenWidth, screenHeight
}

func main() {
	ebiten.SetWindowSize(screenWidth, screenHeight)
	ebiten.SetWindowTitle("Text (Ebiten Demo)")
	if err := ebiten.RunGame(&Game{}); err != nil {
		log.Fatal(err)
	}
}
<|endoftext|>


<commit_before>package renderer

import (
	"image"
	"math"
	"strings"

	"github.com/fogleman/gg"
	"github.com/nomad-software/meme/cli"
	"github.com/nomad-software/meme/font"
	"github.com/nomad-software/meme/output"
)

const (
	FONT_BORDER_RADIUS = 3.0
	FONT_LEADING       = 1.4
	FONT_SIZE_MAX      = 80.0
	IMAGE_MARGIN       = 25.0
)

// Render the meme using the base image.
func Render(options cli.Options, base image.Image) image.Image {
	ctx := gg.NewContextForImage(base)

	if options.Top != "" {
		drawTopBanner(ctx, options.Top)
	}

	if options.Bottom != "" {
		drawBottomBanner(ctx, options.Bottom)
	}

	return ctx.Image()
}

// Draw the top text onto the meme.
func drawTopBanner(ctx *gg.Context, text string) {
	x := float64(ctx.Width()) / 2
	y := IMAGE_MARGIN
	drawText(ctx, text, x, y, 0.5, 0.0)
}

// Draw the bottom text onto the meme.
func drawBottomBanner(ctx *gg.Context, text string) {
	x := float64(ctx.Width()) / 2
	y := float64(ctx.Height()) - IMAGE_MARGIN
	drawText(ctx, text, x, y, 0.5, 1.0)
}

// Draw text onto the meme.
func drawText(ctx *gg.Context, text string, x float64, y float64, ax float64, ay float64) {
	text = strings.ToUpper(text)
	width := float64(ctx.Width()) - (IMAGE_MARGIN * 2)
	height := float64(ctx.Height()) / 3.5
	calculateFontSize(ctx, text, width, height)

	ctx.SetHexColor("#000")
	for angle := 0.0; angle < (2 * math.Pi); angle += 0.35 {
		bx := x + (math.Sin(angle) * FONT_BORDER_RADIUS)
		by := y + (math.Cos(angle) * FONT_BORDER_RADIUS)
		ctx.DrawStringWrapped(text, bx, by, ax, ay, width, FONT_LEADING, gg.AlignCenter)
	}

	ctx.SetHexColor("#FFF")
	ctx.DrawStringWrapped(text, x, y, ax, ay, width, FONT_LEADING, gg.AlignCenter)
}

// Dynamically calculate the correct size needed for text.
func calculateFontSize(ctx *gg.Context, text string, width float64, height float64) {
	for size := FONT_SIZE_MAX; size > 20; size -= 1 {
		var rWidth, rHeight float64
		var lWidth, lHeight float64

		err := ctx.LoadFontFace(font.Path, size)
		output.OnError(err, "Can not load font")
		lines := ctx.WordWrap(text, width)

		for _, line := range lines {
			lWidth, lHeight = ctx.MeasureString(line)

			if lWidth > rWidth {
				rWidth = lWidth
			}
		}

		rHeight = (lHeight * FONT_LEADING) * float64(len(lines))

		if rWidth <= width && rHeight <= height {
			break
		}
	}
}
<commit_msg>Implemented automatic resizing of large images.<commit_after>package renderer

import (
	"image"
	"math"
	"strings"

	"github.com/fogleman/gg"
	"github.com/nfnt/resize"
	"github.com/nomad-software/meme/cli"
	"github.com/nomad-software/meme/font"
	"github.com/nomad-software/meme/output"
)

const (
	FONT_BORDER_RADIUS = 3.0
	FONT_LEADING       = 1.4
	FONT_SIZE_MAX      = 80.0
	IMAGE_MAX_SIZE     = 600
	IMAGE_MARGIN       = 25.0
)

// Render the meme using the base image.
func Render(options cli.Options, base image.Image) image.Image {
	ctx := gg.NewContextForImage(checkSize(base))

	if options.Top != "" {
		drawTopBanner(ctx, options.Top)
	}

	if options.Bottom != "" {
		drawBottomBanner(ctx, options.Bottom)
	}

	return ctx.Image()
}

// Resize the passed image if it is too big.
func checkSize(img image.Image) image.Image {
	if img.Bounds().Dx() > IMAGE_MAX_SIZE {
		img = resize.Resize(IMAGE_MAX_SIZE, 0, img, resize.Bilinear)
	}

	if img.Bounds().Dy() > IMAGE_MAX_SIZE {
		img = resize.Resize(0, IMAGE_MAX_SIZE, img, resize.Bilinear)
	}

	return img
}

// Draw the top text onto the meme.
func drawTopBanner(ctx *gg.Context, text string) {
	x := float64(ctx.Width()) / 2
	y := IMAGE_MARGIN
	drawText(ctx, text, x, y, 0.5, 0.0)
}

// Draw the bottom text onto the meme.
func drawBottomBanner(ctx *gg.Context, text string) {
	x := float64(ctx.Width()) / 2
	y := float64(ctx.Height()) - IMAGE_MARGIN
	drawText(ctx, text, x, y, 0.5, 1.0)
}

// Draw text onto the meme.
func drawText(ctx *gg.Context, text string, x float64, y float64, ax float64, ay float64) {
	text = strings.ToUpper(text)
	width := float64(ctx.Width()) - (IMAGE_MARGIN * 2)
	height := float64(ctx.Height()) / 3.5
	calculateFontSize(ctx, text, width, height)

	ctx.SetHexColor("#000")
	for angle := 0.0; angle < (2 * math.Pi); angle += 0.35 {
		bx := x + (math.Sin(angle) * FONT_BORDER_RADIUS)
		by := y + (math.Cos(angle) * FONT_BORDER_RADIUS)
		ctx.DrawStringWrapped(text, bx, by, ax, ay, width, FONT_LEADING, gg.AlignCenter)
	}

	ctx.SetHexColor("#FFF")
	ctx.DrawStringWrapped(text, x, y, ax, ay, width, FONT_LEADING, gg.AlignCenter)
}

// Dynamically calculate the correct size needed for text.
func calculateFontSize(ctx *gg.Context, text string, width float64, height float64) {
	for size := FONT_SIZE_MAX; size > 20; size -= 1 {
		var rWidth, rHeight float64
		var lWidth, lHeight float64

		err := ctx.LoadFontFace(font.Path, size)
		output.OnError(err, "Can not load font")
		lines := ctx.WordWrap(text, width)

		for _, line := range lines {
			lWidth, lHeight = ctx.MeasureString(line)

			if lWidth > rWidth {
				rWidth = lWidth
			}
		}

		rHeight = (lHeight * FONT_LEADING) * float64(len(lines))

		if rWidth <= width && rHeight <= height {
			break
		}
	}
}
<|endoftext|>


<commit_before>package config

import (
	goflag "flag"
	"github.com/mono83/cfg/flag"
	"github.com/mono83/cfg/pflag"
	gopflag "github.com/ogier/pflag"
	"os"
)

// EnableFlags enables standard library flags reading
func EnableFlags() {
	def.EnableFlags()
}

// EnableFlags enables standard library flags reading
func (c *Config) EnableFlags() {
	c.AddLast(flag.NewFlagSource())
}

// EnableCustomFlags registers configuration source from
// provided FlagSet
func EnableCustomFlags(f *goflag.FlagSet) {
	def.EnableCustomFlags(f)
}

// EnableCustomFlags registers configuration source from
// provided FlagSet
func (c *Config) EnableCustomFlags(f *goflag.FlagSet) {
	c.AddLast(flag.NewCustomFlagSource(f, os.Args[1:]))
}

// EnablePFlags enables improved flags reading
func EnablePFlags() {
	def.EnablePFlags()
}

// EnablePFlags enables improved flags reading
func (c *Config) EnablePFlags() {
	c.AddLast(pflag.NewPFlagSource())
}

// EnableCustomPFlags registers configuration source from
// provided improved PFlagSet
func EnableCustomPFlags(f *gopflag.FlagSet) {
	def.EnableCustomFlags(f)
}

// EnableCustomPFlags registers configuration source from
// provided improved PFlagSet
func (c *Config) EnableCustomPFlags(f *gopflag.FlagSet) {
	c.AddLast(pflag.NewCustomPFlagSource(f, os.Args[1:]))
}
<commit_msg>type fixed<commit_after>package config

import (
	goflag "flag"
	"github.com/mono83/cfg/flag"
	"github.com/mono83/cfg/pflag"
	gopflag "github.com/ogier/pflag"
	"os"
)

// EnableFlags enables standard library flags reading
func EnableFlags() {
	def.EnableFlags()
}

// EnableFlags enables standard library flags reading
func (c *Config) EnableFlags() {
	c.AddLast(flag.NewFlagSource())
}

// EnableCustomFlags registers configuration source from
// provided FlagSet
func EnableCustomFlags(f *goflag.FlagSet) {
	def.EnableCustomFlags(f)
}

// EnableCustomFlags registers configuration source from
// provided FlagSet
func (c *Config) EnableCustomFlags(f *goflag.FlagSet) {
	c.AddLast(flag.NewCustomFlagSource(f, os.Args[1:]))
}

// EnablePFlags enables improved flags reading
func EnablePFlags() {
	def.EnablePFlags()
}

// EnablePFlags enables improved flags reading
func (c *Config) EnablePFlags() {
	c.AddLast(pflag.NewPFlagSource())
}

// EnableCustomPFlags registers configuration source from
// provided improved PFlagSet
func EnableCustomPFlags(f *gopflag.FlagSet) {
	def.EnableCustomPFlags(f)
}

// EnableCustomPFlags registers configuration source from
// provided improved PFlagSet
func (c *Config) EnableCustomPFlags(f *gopflag.FlagSet) {
	c.AddLast(pflag.NewCustomPFlagSource(f, os.Args[1:]))
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"os/exec"
	"os/signal"
	"path"
	"syscall"
	"time"

	"github.com/aerogo/log"
	"github.com/fatih/color"
)

var colorPool = []*color.Color{
	color.New(color.FgHiGreen),
	color.New(color.FgHiYellow),
	color.New(color.FgHiBlue),
	color.New(color.FgHiMagenta),
	color.New(color.FgHiCyan),
	color.New(color.FgGreen),
}

var jobs = map[string]time.Duration{
	"active-users":         1 * time.Minute,
	"forum-activity":       1 * time.Minute,
	"anime-ratings":        10 * time.Minute,
	"airing-anime":         10 * time.Minute,
	"statistics":           15 * time.Minute,
	"popular-anime":        20 * time.Minute,
	"avatars":              30 * time.Minute,
	"twist":                8 * time.Hour,
	"sync-shoboi":          8 * time.Hour,
	"refresh-episodes":     10 * time.Hour,
	"refresh-track-titles": 10 * time.Hour,
	"refresh-osu":          12 * time.Hour,
	"sync-anime":           12 * time.Hour,
	"search-index":         12 * time.Hour,
}

func main() {
	// Start all jobs defined in the map above
	startJobs()

	// Wait for program termination
	wait()
}

func startJobs() {
	// Get the directory the executable is in
	exe, err := os.Executable()

	if err != nil {
		panic(err)
	}

	root := path.Dir(exe)

	// Log paths
	logsPath := path.Join(root, "../", "logs")
	jobLogsPath := path.Join(root, "../", "logs", "jobs")
	os.Mkdir(jobLogsPath, 0777)

	// Scheduler log
	mainLog := log.New()
	mainLog.AddOutput(os.Stdout)
	mainLog.AddOutput(log.File(path.Join(logsPath, "scheduler.log")))
	schedulerLog := mainLog

	// Color index
	colorIndex := 0

	// Start each job
	for job, interval := range jobs {
		jobName := job
		jobInterval := interval
		executable := path.Join(root, jobName, jobName)
		jobColor := colorPool[colorIndex].SprintFunc()

		jobLog := log.New()
		jobLog.AddOutput(log.File(path.Join(jobLogsPath, jobName+".log")))

		fmt.Printf("Registered job %s for execution every %v\n", jobColor(jobName), interval)

		go func() {
			ticker := time.NewTicker(jobInterval)
			defer ticker.Stop()

			var err error

			for {
				// Wait for the given interval first
				<-ticker.C

				// Now start
				schedulerLog.Info("Starting " + jobColor(jobName))

				cmd := exec.Command(executable)
				cmd.Stdout = jobLog
				cmd.Stderr = jobLog

				err = cmd.Start()

				if err != nil {
					schedulerLog.Error("Error starting job", jobColor(jobName), err)
				}

				err = cmd.Wait()

				if err != nil {
					schedulerLog.Error("Job exited with error", jobColor(jobName), err)
				}

				schedulerLog.Info("Finished " + jobColor(jobName))
				jobLog.Info("--------------------------------------------------------------------------------")
			}
		}()

		colorIndex = (colorIndex + 1) % len(colorPool)
	}

	// Finished job registration
	println("--------------------------------------------------------------------------------")
}

func wait() {
	stop := make(chan os.Signal, 1)
	signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
	<-stop
}
<commit_msg>Implemented anime notifications<commit_after>package main

import (
	"fmt"
	"os"
	"os/exec"
	"os/signal"
	"path"
	"syscall"
	"time"

	"github.com/aerogo/log"
	"github.com/fatih/color"
)

var colorPool = []*color.Color{
	color.New(color.FgHiGreen),
	color.New(color.FgHiYellow),
	color.New(color.FgHiBlue),
	color.New(color.FgHiMagenta),
	color.New(color.FgHiCyan),
	color.New(color.FgGreen),
}

var jobs = map[string]time.Duration{
	"active-users":         1 * time.Minute,
	"forum-activity":       1 * time.Minute,
	"anime-ratings":        10 * time.Minute,
	"airing-anime":         10 * time.Minute,
	"statistics":           15 * time.Minute,
	"popular-anime":        20 * time.Minute,
	"avatars":              30 * time.Minute,
	"twist":                1 * time.Hour,
	"sync-shoboi":          8 * time.Hour,
	"refresh-episodes":     10 * time.Hour,
	"refresh-track-titles": 10 * time.Hour,
	"refresh-osu":          12 * time.Hour,
	"sync-anime":           12 * time.Hour,
	"search-index":         12 * time.Hour,
}

func main() {
	// Start all jobs defined in the map above
	startJobs()

	// Wait for program termination
	wait()
}

func startJobs() {
	// Get the directory the executable is in
	exe, err := os.Executable()

	if err != nil {
		panic(err)
	}

	root := path.Dir(exe)

	// Log paths
	logsPath := path.Join(root, "../", "logs")
	jobLogsPath := path.Join(root, "../", "logs", "jobs")
	os.Mkdir(jobLogsPath, 0777)

	// Scheduler log
	mainLog := log.New()
	mainLog.AddOutput(os.Stdout)
	mainLog.AddOutput(log.File(path.Join(logsPath, "scheduler.log")))
	schedulerLog := mainLog

	// Color index
	colorIndex := 0

	// Start each job
	for job, interval := range jobs {
		jobName := job
		jobInterval := interval
		executable := path.Join(root, jobName, jobName)
		jobColor := colorPool[colorIndex].SprintFunc()

		jobLog := log.New()
		jobLog.AddOutput(log.File(path.Join(jobLogsPath, jobName+".log")))

		fmt.Printf("Registered job %s for execution every %v\n", jobColor(jobName), interval)

		go func() {
			ticker := time.NewTicker(jobInterval)
			defer ticker.Stop()

			var err error

			for {
				// Wait for the given interval first
				<-ticker.C

				// Now start
				schedulerLog.Info("Starting " + jobColor(jobName))

				cmd := exec.Command(executable)
				cmd.Stdout = jobLog
				cmd.Stderr = jobLog

				err = cmd.Start()

				if err != nil {
					schedulerLog.Error("Error starting job", jobColor(jobName), err)
				}

				err = cmd.Wait()

				if err != nil {
					schedulerLog.Error("Job exited with error", jobColor(jobName), err)
				}

				schedulerLog.Info("Finished " + jobColor(jobName))
				jobLog.Info("--------------------------------------------------------------------------------")
			}
		}()

		colorIndex = (colorIndex + 1) % len(colorPool)
	}

	// Finished job registration
	println("--------------------------------------------------------------------------------")
}

func wait() {
	stop := make(chan os.Signal, 1)
	signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
	<-stop
}
<|endoftext|>


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

package twodee

import (
	gmath "github.com/Agon/googlmath"
)

type Matrix4 [16]float32

func (m Matrix4) Mul(a Matrix4) {
	n := getMatrix(getGMathMatrix(m).Mul(getGMathMatrix(a)))
	for i := 0; i < 16; i++ {
		m[i] = n[i]
	}
}

func getMatrix(m gmath.Matrix4) Matrix4 {
	return Matrix4{
		m.M11, m.M12, m.M13, m.M14,
		m.M21, m.M22, m.M23, m.M24,
		m.M31, m.M32, m.M33, m.M34,
		m.M41, m.M42, m.M43, m.M44,
	}
}

func getGMathMatrix(m Matrix4) gmath.Matrix4 {
	return gmath.Matrix4{
		m[0], m[1], m[2], m[3],
		m[4], m[5], m[6], m[7],
		m[8], m[9], m[10], m[11],
		m[12], m[13], m[14], m[15],
	}
}

func GetTranslationMatrix(x, y, z float32) Matrix4 {
	return getMatrix(gmath.TranslationMatrix4(x, y, z))
}

func GetRotationMatrix(x, y, z, a float32) Matrix4 {
	axis := gmath.Vector3{x, y, z}
	return getMatrix(gmath.RotationMatrix4(axis, a))
}

func GetRotTransMatrix(x, y, z, a float32) Matrix4 {
	var (
		axis  = gmath.Vector3{0, 0, 1}
		trans = gmath.TranslationMatrix4(x, y, z)
		rot   = gmath.RotationMatrix4(axis, a)
	)
	return getMatrix(trans.Mul(rot))
}

func GetRotTransScaleMatrix(x, y, z, a, s float32) Matrix4 {
	var (
		axis  = gmath.Vector3{0, 0, 1}
		trans = gmath.TranslationMatrix4(x, y, z)
		rot   = gmath.RotationMatrix4(axis, a)
		scale = getGMathMatrix(GetScaleMatrix(s, s, 0))
	)
	return getMatrix(trans.Mul(rot).Mul(scale))
}

func GetScaleMatrix(x, y, z float32) Matrix4 {
	return Matrix4{
		x, 0, 0, 0,
		0, y, 0, 0,
		0, 0, z, 0,
		0, 0, 0, 1,
	}
}

func GetOrthoMatrix(x1, x2, y1, y2, n, f float32) Matrix4 {
	// http://www.songho.ca/opengl/gl_projectionmatrix.html
	return Matrix4{
		2.0 / (x2 - x1), 0, 0, 0,
		0, 2.0 / (y2 - y1), 0, 0,
		0, 0, -2.0 / (f - n), 0,
		-(x2 + x1) / (x2 - x1), -(y2 + y1) / (y2 - y1), -(f + n) / (f - n), 1,
	}
}

func GetInverseMatrix(m Matrix4) (out Matrix4, err error) {
	var (
		inv gmath.Matrix4
	)
	if inv, err = getGMathMatrix(m).Invert(); err != nil {
		return
	}
	out = getMatrix(inv)
	return
}

func Unproject(invproj Matrix4, x float32, y float32) (wx, wy float32) {
	var (
		screen = gmath.Vector4{x, y, 1, 1}
		out    gmath.Vector4
	)
	out = getGMathMatrix(invproj).MulVec4(screen)
	out.Scale(1.0 / out.W)
	wx = out.X
	wy = out.Y
	return
}

func Project(proj Matrix4, x float32, y float32) (sx, sy float32) {
	var out gmath.Vector4
	out = getGMathMatrix(proj).MulVec4(gmath.Vector4{x, y, 1, 1})
	return out.X, out.Y
}
<commit_msg>Update matrix<commit_after>// Copyright 2014 Arne Roomann-Kurrik
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package twodee

import (
	"github.com/go-gl/mathgl/mgl32"
)

func GetTranslationMatrix(x, y, z float32) mgl32.Mat4 {
	return mgl32.Translate3D(x, y, z)
}

func GetRotationMatrix(x, y, z, a float32) mgl32.Mat4 {
	axis := mgl32.Vec3{x, y, z}
	return HomogRotate3D(a, axis)
}

func GetScaleMatrix(x, y, z float32) mgl32.Mat4 {
	return Scale3D(x, y, z)
}

func GetRotTransMatrix(x, y, z, a float32) mgl32.Mat4 {
	var (
		trans = GetTranslationMatrix(x, y, z)
		rot   = GetRotationMatrix(0, 0, 1, a)
	)
	return trans.Mul(rot)
}

func GetRotTransScaleMatrix(x, y, z, a, s float32) mgl32.Mat4 {
	var (
		trans = GetTranslationMatrix(x, y, z)
		rot   = GetRotationMatrix(0, 0, 1, a)
		scale = GetScaleMatrix(s, s, 1)
	)
	return trans.Mul(rot).Mul(scale)
}

func GetOrthoMatrix(x1, x2, y1, y2, n, f float32) mgl32.Mat4 {
	return mgl32.Ortho(x1, x2, y1, y2, near, far)
}

func GetInverseMatrix(m mgl32.Mat4) (out mgl32.Mat4, err error) {
	var inv = m.Inv()
	if inv == mgl.Mat4 {
		err = fmt.Errorf("Cannot invert matrix")
	}
	return
}

func Unproject(invproj mgl32.Mat4, x float32, y float32) (wx, wy float32) {
	var (
		screen = mgl32.Vec4{x, y, 1, 1}
		out    mgl32.Vec4
	)
	out = invproj.Mul4x1(screen).Mul(1.0 / out.W)
	wx = out.X()
	wy = out.Y()
	return
}

func Project(proj mgl32.Mat4, x float32, y float32) (sx, sy float32) {
	var out mgl32.Vec4
	out = proj.Mul4x1(mgl32.Vec4{x, y, 1, 1})
	return out.X, out.Y
}
<|endoftext|>


<commit_before>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package syslog

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"path/filepath"
	"strings"
	"text/template"
)

// tagOffset represents the substring start value for the tag to return
// the logfileName value from the syslogtag.  Substrings in syslog are
// indexed from 1, hence the + 1.
const tagOffset = len("juju-") + 1

// The rsyslog conf for state server nodes.
// Messages are gathered from other nodes and accumulated in an all-machines.log file.
const stateServerRsyslogTemplate = `
$ModLoad imfile

$InputFilePersistStateInterval 50
$InputFilePollInterval 5
$InputFileName {{logfilePath}}
$InputFileTag juju{{namespace}}-{{logfileName}}:
$InputFileStateFile {{logfileName}}{{namespace}}
$InputRunFileMonitor

$ModLoad imudp
$UDPServerRun {{portNumber}}

# Messages received from remote rsyslog machines have messages prefixed with a space,
# so add one in for local messages too if needed.
$template JujuLogFormat{{namespace}},"%syslogtag:{{tagStart}}:$%%msg:::sp-if-no-1st-sp%%msg:::drop-last-lf%\n"

if $syslogtag startswith "juju{{namespace}}-" then
  action(type="omfile"
         File="/var/log/juju{{namespace}}/all-machines.log"
         Template="JujuLogFormat{{namespace}}"
         FileCreateMode="0644")
& stop
`

// The rsyslog conf for non-state server nodes.
// Messages are forwarded to the state server node.
const nodeRsyslogTemplate = `
$ModLoad imfile

$InputFilePersistStateInterval 50
$InputFilePollInterval 5
$InputFileName {{logfilePath}}
$InputFileTag juju{{namespace}}-{{logfileName}}:
$InputFileStateFile {{logfileName}}{{namespace}}
$InputRunFileMonitor

$template LongTagForwardFormat,"<%PRI%>%TIMESTAMP:::date-rfc3339% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg%"

:syslogtag, startswith, "juju{{namespace}}-" @{{bootstrapIP}}:{{portNumber}};LongTagForwardFormat
& ~
`

const defaultConfigDir = "/etc/rsyslog.d"

// SyslogConfigRenderer instances are used to generate a rsyslog conf file.
type SyslogConfigRenderer interface {
	Render() ([]byte, error)
}

// SyslogConfig provides a means to configure and generate rsyslog conf files for
// the state server nodes and unit nodes.
// rsyslog is configured to tail the specified log file.
type SyslogConfig struct {
	// the template representing the config file contents.
	configTemplate string
	// the directory where the config file is written.
	ConfigDir string
	// the config file name.
	ConfigFileName string
	// the name of the log file to tail.
	LogFileName string
	// the addresses of the state server to which messages should be forwarded.
	StateServerAddresses []string
	// the port number for the udp listener
	Port int
	// the directory for the logfiles
	LogDir string
	// namespace is used when there are multiple environments on one machine
	Namespace string
}

// NewForwardConfig creates a SyslogConfig instance used on unit nodes to forward log entries
// to the state server nodes.
func NewForwardConfig(logFile string, port int, namespace string, stateServerAddresses []string) *SyslogConfig {
	conf := &SyslogConfig{
		configTemplate:       nodeRsyslogTemplate,
		StateServerAddresses: stateServerAddresses,
		LogFileName:          logFile,
		Port:                 port,
		LogDir:               "/var/log/juju",
	}
	if namespace != "" {
		conf.Namespace = "-" + namespace
	}
	return conf
}

// NewAccumulateConfig creates a SyslogConfig instance used to accumulate log entries from the
// various unit nodes.
func NewAccumulateConfig(logFile string, port int, namespace string) *SyslogConfig {
	conf := &SyslogConfig{
		configTemplate: stateServerRsyslogTemplate,
		LogFileName:    logFile,
		Port:           port,
		LogDir:         "/var/log/juju",
	}
	if namespace != "" {
		conf.Namespace = "-" + namespace
	}
	return conf
}

func (slConfig *SyslogConfig) ConfigFilePath() string {
	dir := slConfig.ConfigDir
	if dir == "" {
		dir = defaultConfigDir
	}
	return filepath.Join(dir, slConfig.ConfigFileName)
}

// Render generates the rsyslog config.
func (slConfig *SyslogConfig) Render() ([]byte, error) {

	// TODO: for HA, we will want to send to all state server addresses (maybe).
	var bootstrapIP = func() string {
		addr := slConfig.StateServerAddresses[0]
		parts := strings.Split(addr, ":")
		return parts[0]
	}

	var logFilePath = func() string {
		return fmt.Sprintf("%s/%s.log", slConfig.LogDir, slConfig.LogFileName)
	}

	t := template.New("")
	t.Funcs(template.FuncMap{
		"logfileName": func() string { return slConfig.LogFileName },
		"bootstrapIP": bootstrapIP,
		"logfilePath": logFilePath,
		"portNumber":  func() int { return slConfig.Port },
		"logDir":      func() string { return slConfig.LogDir },
		"namespace":   func() string { return slConfig.Namespace },
		"tagStart":    func() int { return tagOffset + len(slConfig.Namespace) },
	})

	// Process the rsyslog config template and echo to the conf file.
	p, err := t.Parse(slConfig.configTemplate)
	if err != nil {
		return nil, err
	}
	var confBuf bytes.Buffer
	if err := p.Execute(&confBuf, nil); err != nil {
		return nil, err
	}
	return confBuf.Bytes(), nil
}

// Write generates and writes the rsyslog config.
func (slConfig *SyslogConfig) Write() error {
	data, err := slConfig.Render()
	if err != nil {
		return err
	}
	err = ioutil.WriteFile(slConfig.ConfigFilePath(), data, 0644)
	return err
}
<commit_msg>Have to revert to modifying the globals.<commit_after>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package syslog

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"path/filepath"
	"strings"
	"text/template"
)

// tagOffset represents the substring start value for the tag to return
// the logfileName value from the syslogtag.  Substrings in syslog are
// indexed from 1, hence the + 1.
const tagOffset = len("juju-") + 1

// The rsyslog conf for state server nodes.
// Messages are gathered from other nodes and accumulated in an all-machines.log file.
//
// I would dearly love to write the filtering action as follows to avoid setting
// and resetting the global $FileCreateMode, but alas, precise doesn't support it
//
// if $syslogtag startswith "juju{{namespace}}-" then
//   action(type="omfile"
//          File="/var/log/juju{{namespace}}/all-machines.log"
//          Template="JujuLogFormat{{namespace}}"
//          FileCreateMode="0644")
// & stop
const stateServerRsyslogTemplate = `
$ModLoad imfile

$InputFilePersistStateInterval 50
$InputFilePollInterval 5
$InputFileName {{logfilePath}}
$InputFileTag juju{{namespace}}-{{logfileName}}:
$InputFileStateFile {{logfileName}}{{namespace}}
$InputRunFileMonitor

$ModLoad imudp
$UDPServerRun {{portNumber}}

# Messages received from remote rsyslog machines have messages prefixed with a space,
# so add one in for local messages too if needed.
$template JujuLogFormat{{namespace}},"%syslogtag:{{tagStart}}:$%%msg:::sp-if-no-1st-sp%%msg:::drop-last-lf%\n"

$FileCreateMode 0644
:syslogtag, startswith, "juju{{namespace}}-" /var/log/juju{{namespace}}/all-machines.log;JujuLogFormat{{namespace}}
& ~
$FileCreateMode 0640
`

// The rsyslog conf for non-state server nodes.
// Messages are forwarded to the state server node.
const nodeRsyslogTemplate = `
$ModLoad imfile

$InputFilePersistStateInterval 50
$InputFilePollInterval 5
$InputFileName {{logfilePath}}
$InputFileTag juju{{namespace}}-{{logfileName}}:
$InputFileStateFile {{logfileName}}{{namespace}}
$InputRunFileMonitor

$template LongTagForwardFormat,"<%PRI%>%TIMESTAMP:::date-rfc3339% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg%"

:syslogtag, startswith, "juju{{namespace}}-" @{{bootstrapIP}}:{{portNumber}};LongTagForwardFormat
& ~
`

const defaultConfigDir = "/etc/rsyslog.d"

// SyslogConfigRenderer instances are used to generate a rsyslog conf file.
type SyslogConfigRenderer interface {
	Render() ([]byte, error)
}

// SyslogConfig provides a means to configure and generate rsyslog conf files for
// the state server nodes and unit nodes.
// rsyslog is configured to tail the specified log file.
type SyslogConfig struct {
	// the template representing the config file contents.
	configTemplate string
	// the directory where the config file is written.
	ConfigDir string
	// the config file name.
	ConfigFileName string
	// the name of the log file to tail.
	LogFileName string
	// the addresses of the state server to which messages should be forwarded.
	StateServerAddresses []string
	// the port number for the udp listener
	Port int
	// the directory for the logfiles
	LogDir string
	// namespace is used when there are multiple environments on one machine
	Namespace string
}

// NewForwardConfig creates a SyslogConfig instance used on unit nodes to forward log entries
// to the state server nodes.
func NewForwardConfig(logFile string, port int, namespace string, stateServerAddresses []string) *SyslogConfig {
	conf := &SyslogConfig{
		configTemplate:       nodeRsyslogTemplate,
		StateServerAddresses: stateServerAddresses,
		LogFileName:          logFile,
		Port:                 port,
		LogDir:               "/var/log/juju",
	}
	if namespace != "" {
		conf.Namespace = "-" + namespace
	}
	return conf
}

// NewAccumulateConfig creates a SyslogConfig instance used to accumulate log entries from the
// various unit nodes.
func NewAccumulateConfig(logFile string, port int, namespace string) *SyslogConfig {
	conf := &SyslogConfig{
		configTemplate: stateServerRsyslogTemplate,
		LogFileName:    logFile,
		Port:           port,
		LogDir:         "/var/log/juju",
	}
	if namespace != "" {
		conf.Namespace = "-" + namespace
	}
	return conf
}

func (slConfig *SyslogConfig) ConfigFilePath() string {
	dir := slConfig.ConfigDir
	if dir == "" {
		dir = defaultConfigDir
	}
	return filepath.Join(dir, slConfig.ConfigFileName)
}

// Render generates the rsyslog config.
func (slConfig *SyslogConfig) Render() ([]byte, error) {

	// TODO: for HA, we will want to send to all state server addresses (maybe).
	var bootstrapIP = func() string {
		addr := slConfig.StateServerAddresses[0]
		parts := strings.Split(addr, ":")
		return parts[0]
	}

	var logFilePath = func() string {
		return fmt.Sprintf("%s/%s.log", slConfig.LogDir, slConfig.LogFileName)
	}

	t := template.New("")
	t.Funcs(template.FuncMap{
		"logfileName": func() string { return slConfig.LogFileName },
		"bootstrapIP": bootstrapIP,
		"logfilePath": logFilePath,
		"portNumber":  func() int { return slConfig.Port },
		"logDir":      func() string { return slConfig.LogDir },
		"namespace":   func() string { return slConfig.Namespace },
		"tagStart":    func() int { return tagOffset + len(slConfig.Namespace) },
	})

	// Process the rsyslog config template and echo to the conf file.
	p, err := t.Parse(slConfig.configTemplate)
	if err != nil {
		return nil, err
	}
	var confBuf bytes.Buffer
	if err := p.Execute(&confBuf, nil); err != nil {
		return nil, err
	}
	return confBuf.Bytes(), nil
}

// Write generates and writes the rsyslog config.
func (slConfig *SyslogConfig) Write() error {
	data, err := slConfig.Render()
	if err != nil {
		return err
	}
	err = ioutil.WriteFile(slConfig.ConfigFilePath(), data, 0644)
	return err
}
<|endoftext|>


<commit_before>package congestion_test

import (
	"time"

	"github.com/lucas-clemente/quic-go/congestion"
	"github.com/lucas-clemente/quic-go/protocol"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

const kBeta float32 = 0.7 // Default Cubic backoff factor.
const kNumConnections uint32 = 2
const kNConnectionBeta float32 = (float32(kNumConnections) - 1 + kBeta) / float32(kNumConnections)
const kNConnectionAlpha float32 = 3 * float32(kNumConnections) * float32(kNumConnections) * (1 - kNConnectionBeta) / (1 + kNConnectionBeta)

var _ = Describe("Cubic", func() {
	var (
		clock mockClock
		cubic *congestion.Cubic
	)

	BeforeEach(func() {
		clock = mockClock{}
		cubic = congestion.NewCubic(&clock)
	})

	It("works above origin", func() {
		// Convex growth.
		rtt_min := 100 * time.Millisecond
		current_cwnd := protocol.PacketNumber(10)
		expected_cwnd := current_cwnd + 1
		// Initialize the state.
		clock.Advance(time.Millisecond)
		Expect(cubic.CongestionWindowAfterAck(current_cwnd, rtt_min)).To(Equal(expected_cwnd))
		current_cwnd = expected_cwnd
		// Normal TCP phase.
		for i := 0; i < 48; i++ {
			for n := uint64(1); n < uint64(float32(current_cwnd)/kNConnectionAlpha); n++ {
				// Call once per ACK.
				Expect(cubic.CongestionWindowAfterAck(current_cwnd, rtt_min)).To(BeNumerically("~", current_cwnd, 1))
			}
			clock.Advance(100 * time.Millisecond)
			current_cwnd = cubic.CongestionWindowAfterAck(current_cwnd, rtt_min)
			Expect(current_cwnd).To(BeNumerically("~", expected_cwnd, 1))
			expected_cwnd++
		}
		// Cubic phase.
		for i := 0; i < 52; i++ {
			for n := protocol.PacketNumber(1); n < current_cwnd; n++ {
				// Call once per ACK.
				Expect(cubic.CongestionWindowAfterAck(current_cwnd, rtt_min)).To(Equal(current_cwnd))
			}
			clock.Advance(100 * time.Millisecond)
			current_cwnd = cubic.CongestionWindowAfterAck(current_cwnd, rtt_min)
		}
		// Total time elapsed so far; add min_rtt (0.1s) here as well.
		elapsed_time_s := 10.0 + 0.1
		// |expected_cwnd| is initial value of cwnd + K * t^3, where K = 0.4.
		expected_cwnd = protocol.PacketNumber(11 + (elapsed_time_s*elapsed_time_s*elapsed_time_s*410)/1024)
		Expect(current_cwnd).To(Equal(expected_cwnd))
	})

	// TODO: Test copied from Chromium has no assertions
	It("has increasing cwnd stats during convex region", func() {
		rtt_min := 100 * time.Millisecond
		current_cwnd := protocol.PacketNumber(10)
		expected_cwnd := current_cwnd + 1
		// Initialize the state.
		clock.Advance(time.Millisecond)
		expected_cwnd = cubic.CongestionWindowAfterAck(current_cwnd, rtt_min)
		current_cwnd = expected_cwnd
		// Testing Reno mode increase.
		for i := 0; i < 48; i++ {
			for n := uint64(1); n < uint64(float32(current_cwnd)/kNConnectionAlpha); n++ {
				// Call once per ACK, causing cwnd growth in Reno mode.
				cubic.CongestionWindowAfterAck(current_cwnd, rtt_min)
			}
			// Advance current time so that cwnd update is allowed to happen by Cubic.
			clock.Advance(100 * time.Millisecond)
			current_cwnd = cubic.CongestionWindowAfterAck(current_cwnd, rtt_min)
			expected_cwnd++
		}

		// Testing Cubic mode increase.
		for i := 0; i < 52; i++ {
			for n := protocol.PacketNumber(1); n < current_cwnd; n++ {
				// Call once per ACK.
				cubic.CongestionWindowAfterAck(current_cwnd, rtt_min)
			}
			clock.Advance(100 * time.Millisecond)
			current_cwnd = cubic.CongestionWindowAfterAck(current_cwnd, rtt_min)
		}
	})

	It("manages loss events", func() {
		rtt_min := 100 * time.Millisecond
		current_cwnd := protocol.PacketNumber(422)
		expected_cwnd := current_cwnd + 1
		// Initialize the state.
		clock.Advance(time.Millisecond)
		Expect(cubic.CongestionWindowAfterAck(current_cwnd, rtt_min)).To(Equal(expected_cwnd))
		expected_cwnd = protocol.PacketNumber(float32(current_cwnd) * kNConnectionBeta)
		Expect(cubic.CongestionWindowAfterPacketLoss(current_cwnd)).To(Equal(expected_cwnd))
		expected_cwnd = protocol.PacketNumber(float32(current_cwnd) * kNConnectionBeta)
		Expect(cubic.CongestionWindowAfterPacketLoss(current_cwnd)).To(Equal(expected_cwnd))
	})

	It("works below origin", func() {
		// Concave growth.
		rtt_min := 100 * time.Millisecond
		current_cwnd := protocol.PacketNumber(422)
		expected_cwnd := current_cwnd + 1
		// Initialize the state.
		clock.Advance(time.Millisecond)
		Expect(cubic.CongestionWindowAfterAck(current_cwnd, rtt_min)).To(Equal(expected_cwnd))
		expected_cwnd = protocol.PacketNumber(float32(current_cwnd) * kNConnectionBeta)
		Expect(cubic.CongestionWindowAfterPacketLoss(current_cwnd)).To(Equal(expected_cwnd))
		current_cwnd = expected_cwnd
		// First update after loss to initialize the epoch.
		current_cwnd = cubic.CongestionWindowAfterAck(current_cwnd, rtt_min)
		// Cubic phase.
		for i := 0; i < 40; i++ {
			clock.Advance(100 * time.Millisecond)
			current_cwnd = cubic.CongestionWindowAfterAck(current_cwnd, rtt_min)
		}
		expected_cwnd = 422
		Expect(current_cwnd).To(Equal(expected_cwnd))
	})
})
<commit_msg>remove unnecessary cubic test<commit_after>package congestion_test

import (
	"time"

	"github.com/lucas-clemente/quic-go/congestion"
	"github.com/lucas-clemente/quic-go/protocol"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

const kBeta float32 = 0.7 // Default Cubic backoff factor.
const kNumConnections uint32 = 2
const kNConnectionBeta float32 = (float32(kNumConnections) - 1 + kBeta) / float32(kNumConnections)
const kNConnectionAlpha float32 = 3 * float32(kNumConnections) * float32(kNumConnections) * (1 - kNConnectionBeta) / (1 + kNConnectionBeta)

var _ = Describe("Cubic", func() {
	var (
		clock mockClock
		cubic *congestion.Cubic
	)

	BeforeEach(func() {
		clock = mockClock{}
		cubic = congestion.NewCubic(&clock)
	})

	It("works above origin", func() {
		// Convex growth.
		rtt_min := 100 * time.Millisecond
		current_cwnd := protocol.PacketNumber(10)
		expected_cwnd := current_cwnd + 1
		// Initialize the state.
		clock.Advance(time.Millisecond)
		Expect(cubic.CongestionWindowAfterAck(current_cwnd, rtt_min)).To(Equal(expected_cwnd))
		current_cwnd = expected_cwnd
		// Normal TCP phase.
		for i := 0; i < 48; i++ {
			for n := uint64(1); n < uint64(float32(current_cwnd)/kNConnectionAlpha); n++ {
				// Call once per ACK.
				Expect(cubic.CongestionWindowAfterAck(current_cwnd, rtt_min)).To(BeNumerically("~", current_cwnd, 1))
			}
			clock.Advance(100 * time.Millisecond)
			current_cwnd = cubic.CongestionWindowAfterAck(current_cwnd, rtt_min)
			Expect(current_cwnd).To(BeNumerically("~", expected_cwnd, 1))
			expected_cwnd++
		}
		// Cubic phase.
		for i := 0; i < 52; i++ {
			for n := protocol.PacketNumber(1); n < current_cwnd; n++ {
				// Call once per ACK.
				Expect(cubic.CongestionWindowAfterAck(current_cwnd, rtt_min)).To(Equal(current_cwnd))
			}
			clock.Advance(100 * time.Millisecond)
			current_cwnd = cubic.CongestionWindowAfterAck(current_cwnd, rtt_min)
		}
		// Total time elapsed so far; add min_rtt (0.1s) here as well.
		elapsed_time_s := 10.0 + 0.1
		// |expected_cwnd| is initial value of cwnd + K * t^3, where K = 0.4.
		expected_cwnd = protocol.PacketNumber(11 + (elapsed_time_s*elapsed_time_s*elapsed_time_s*410)/1024)
		Expect(current_cwnd).To(Equal(expected_cwnd))
	})

	It("manages loss events", func() {
		rtt_min := 100 * time.Millisecond
		current_cwnd := protocol.PacketNumber(422)
		expected_cwnd := current_cwnd + 1
		// Initialize the state.
		clock.Advance(time.Millisecond)
		Expect(cubic.CongestionWindowAfterAck(current_cwnd, rtt_min)).To(Equal(expected_cwnd))
		expected_cwnd = protocol.PacketNumber(float32(current_cwnd) * kNConnectionBeta)
		Expect(cubic.CongestionWindowAfterPacketLoss(current_cwnd)).To(Equal(expected_cwnd))
		expected_cwnd = protocol.PacketNumber(float32(current_cwnd) * kNConnectionBeta)
		Expect(cubic.CongestionWindowAfterPacketLoss(current_cwnd)).To(Equal(expected_cwnd))
	})

	It("works below origin", func() {
		// Concave growth.
		rtt_min := 100 * time.Millisecond
		current_cwnd := protocol.PacketNumber(422)
		expected_cwnd := current_cwnd + 1
		// Initialize the state.
		clock.Advance(time.Millisecond)
		Expect(cubic.CongestionWindowAfterAck(current_cwnd, rtt_min)).To(Equal(expected_cwnd))
		expected_cwnd = protocol.PacketNumber(float32(current_cwnd) * kNConnectionBeta)
		Expect(cubic.CongestionWindowAfterPacketLoss(current_cwnd)).To(Equal(expected_cwnd))
		current_cwnd = expected_cwnd
		// First update after loss to initialize the epoch.
		current_cwnd = cubic.CongestionWindowAfterAck(current_cwnd, rtt_min)
		// Cubic phase.
		for i := 0; i < 40; i++ {
			clock.Advance(100 * time.Millisecond)
			current_cwnd = cubic.CongestionWindowAfterAck(current_cwnd, rtt_min)
		}
		expected_cwnd = 422
		Expect(current_cwnd).To(Equal(expected_cwnd))
	})
})
<|endoftext|>


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

package connection

import (
	"bytes"
	"errors"
	"fmt"

	compose "github.com/compose/gocomposeapi"
)

type Deployment interface {
	GetName() string
	GetCluster() string
	GetType() string
	GetNotes() string
	GetScaling() int
	GetSSL() bool
	GetTeams() map[string]([]string)
}

type Connection struct {
	client              *compose.Client
	accountID           string
	clusterIDsByName    map[string]string
	deploymentIDsByName map[string]string
}

func Init(apiKey string, verbose bool) (*Connection, error) {
	cxn := &Connection{}
	var err error

	cxn.client, err = createClient(apiKey)
	if err != nil {
		return cxn, err
	}

	cxn.accountID, err = fetchAccountID(cxn.client)
	if err != nil {
		return cxn, err
	}

	cxn.clusterIDsByName, err = fetchClusters(cxn.client)
	if err != nil {
		return cxn, err
	}

	cxn.deploymentIDsByName, err = fetchDeployments(cxn.client)

	return cxn, err
}

func Provision(cxn *Connection, deployment Deployment, verbose bool) error {
	if _, ok := cxn.deploymentIDsByName[deployment.GetName()]; ok {
		return rescale(cxn, deployment, verbose)
	}
	return provision(cxn, deployment, verbose)
}

func rescale(cxn *Connection, deployment Deployment, verbose bool) error {
	fmt.Println("I would totally resize something here!")
	return nil
}

func provision(cxn *Connection, deployment Deployment, verbose bool) error {
	if verbose {
		fmt.Printf("Provisioning '%s' in '%s'...\n", deployment.GetName(),
			deployment.GetCluster())
	}

	clusterID, ok := cxn.clusterIDsByName[deployment.GetCluster()]
	if !ok {
		return fmt.Errorf("Unable to provsion '%s'. The specified cluster name, '%s' does not map to a known cluster.",
			deployment.GetName(), deployment.GetCluster())
	}

	dParams := compose.DeploymentParams{
		Name:         deployment.GetName(),
		AccountID:    cxn.accountID,
		ClusterID:    clusterID,
		DatabaseType: deployment.GetType(),
		Notes:        deployment.GetNotes(),
		// Mising WiredTiger!!!
	}

	if deployment.GetScaling() > 1 {
		dParams.Units = deployment.GetScaling()
	}
	if deployment.GetSSL() {
		dParams.SSL = true
	}

	//This needs to be wrapped in retry logic
	newDeployment, errs := cxn.client.CreateDeployment(dParams)
	if errs != nil {
		return fmt.Errorf("Unable to create '%s': %s\n", errsOut(errs))
	}
	if verbose {
		fmt.Printf("Provision of '%s' is complete!\n", newDeployment.Name)
	}
	cxn.deploymentIDsByName[newDeployment.ID] = newDeployment.Name

	return nil
}

func createClient(apiKey string) (*compose.Client, error) {
	if len(apiKey) == 0 {
		return nil, errors.New("No API key found. Specify one using the --api-key flag or the COMPOSE_API_KEY environment variable")
	}

	return compose.NewClient(apiKey)
}

func fetchAccountID(client *compose.Client) (string, error) {
	account, errs := client.GetAccount()
	if len(errs) != 0 {
		return "", fmt.Errorf("Failed to get account id:\n%s", errsOut(errs))
	}

	return account.ID, nil
}

func fetchClusters(client *compose.Client) (map[string]string, error) {
	clusterIDsByName := make(map[string]string)

	clusters, errs := client.GetClusters()
	if len(errs) != 0 {
		return clusterIDsByName, fmt.Errorf("Failed to get cluster information:\n%s",
			errsOut(errs))
	}

	if clusters == nil {
		return clusterIDsByName, fmt.Errorf("No clusters found")
	}

	for _, cluster := range *clusters {
		clusterIDsByName[cluster.Name] = cluster.ID
	}
	return clusterIDsByName, nil
}

func fetchDeployments(client *compose.Client) (map[string]string, error) {
	deploymentIDsByName := make(map[string]string)
	deployments, errs := client.GetDeployments()
	if len(errs) != 0 {
		return deploymentIDsByName, fmt.Errorf("Failed to get deployments:\n%s",
			errsOut(errs))
	}

	if deployments == nil {
		// This is not necessarily an error.
		return deploymentIDsByName, nil
	}

	for _, deployment := range *deployments {
		deploymentIDsByName[deployment.Name] = deployment.ID
	}
	return deploymentIDsByName, nil
}

func errsOut(errs []error) string {
	var buf bytes.Buffer
	for _, err := range errs {
		if _, wErr := buf.WriteString(err.Error()); wErr != nil {
			panic(wErr)
		}
		if _, wErr := buf.WriteString("\n"); wErr != nil {
			panic(wErr)
		}
	}
	return buf.String()
}
<commit_msg>Add support for resizing deployments that already exist<commit_after>// Copyright © 2017 ben dewan <benj.dewan@gmail.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package connection

import (
	"bytes"
	"errors"
	"fmt"

	compose "github.com/compose/gocomposeapi"
)

type Deployment interface {
	GetName() string
	GetCluster() string
	GetType() string
	GetNotes() string
	GetScaling() int
	GetSSL() bool
	GetTeams() map[string]([]string)
}

type Connection struct {
	client              *compose.Client
	accountID           string
	clusterIDsByName    map[string]string
	deploymentIDsByName map[string]string
}

func Init(apiKey string, verbose bool) (*Connection, error) {
	cxn := &Connection{}
	var err error

	cxn.client, err = createClient(apiKey)
	if err != nil {
		return cxn, err
	}

	cxn.accountID, err = fetchAccountID(cxn.client)
	if err != nil {
		return cxn, err
	}

	cxn.clusterIDsByName, err = fetchClusters(cxn.client)
	if err != nil {
		return cxn, err
	}

	cxn.deploymentIDsByName, err = fetchDeployments(cxn.client)

	return cxn, err
}

func Provision(cxn *Connection, deployment Deployment, verbose bool) error {
	if id, ok := cxn.deploymentIDsByName[deployment.GetName()]; ok {
		return rescale(cxn, id, deployment, verbose)
	}
	return provision(cxn, deployment, verbose)
}

func rescale(cxn *Connection, deploymentID string, deployment Deployment, verbose bool) error {
	scalings, errs := cxn.client.GetScalings(deploymentID)
	if len(errs) != 0 {
		return fmt.Errorf("Unable get scaling status for '%s': %v",
			deployment.GetName(), errsOut(errs))
	}
	if scalings.AllocatedUnits == deployment.GetScaling() {
		if verbose {
			fmt.Printf("Nothing to do for '%s'\n", deployment.GetName())
		}
		return nil
	}

	sParams := compose.ScalingsParams{
		DeploymentID: deploymentID,
		Units:        deployment.GetScaling(),
	}
	_, errs = cxn.client.SetScalings(sParams)
	if len(errs) != 0 {
		return fmt.Errorf("Unable to resize '%s': %v\n",
			deployment.GetName(), errsOut(errs))
	}
	return nil
}

func provision(cxn *Connection, deployment Deployment, verbose bool) error {
	if verbose {
		fmt.Printf("Provisioning '%s' in '%s'...\n", deployment.GetName(),
			deployment.GetCluster())
	}

	clusterID, ok := cxn.clusterIDsByName[deployment.GetCluster()]
	if !ok {
		return fmt.Errorf("Unable to provsion '%s'. The specified cluster name, '%s' does not map to a known cluster.",
			deployment.GetName(), deployment.GetCluster())
	}

	dParams := compose.DeploymentParams{
		Name:         deployment.GetName(),
		AccountID:    cxn.accountID,
		ClusterID:    clusterID,
		DatabaseType: deployment.GetType(),
		Notes:        deployment.GetNotes(),
		// Mising WiredTiger!!!
	}

	if deployment.GetScaling() > 1 {
		dParams.Units = deployment.GetScaling()
	}
	if deployment.GetSSL() {
		dParams.SSL = true
	}

	//This needs to be wrapped in retry logic
	newDeployment, errs := cxn.client.CreateDeployment(dParams)
	if errs != nil {
		return fmt.Errorf("Unable to create '%s': %s\n", errsOut(errs))
	}
	if verbose {
		fmt.Printf("Provision of '%s' is complete!\n", newDeployment.Name)
	}
	cxn.deploymentIDsByName[newDeployment.ID] = newDeployment.Name

	return nil
}

func createClient(apiKey string) (*compose.Client, error) {
	if len(apiKey) == 0 {
		return nil, errors.New("No API key found. Specify one using the --api-key flag or the COMPOSE_API_KEY environment variable")
	}

	return compose.NewClient(apiKey)
}

func fetchAccountID(client *compose.Client) (string, error) {
	account, errs := client.GetAccount()
	if len(errs) != 0 {
		return "", fmt.Errorf("Failed to get account id:\n%s", errsOut(errs))
	}

	return account.ID, nil
}

func fetchClusters(client *compose.Client) (map[string]string, error) {
	clusterIDsByName := make(map[string]string)

	clusters, errs := client.GetClusters()
	if len(errs) != 0 {
		return clusterIDsByName, fmt.Errorf("Failed to get cluster information:\n%s",
			errsOut(errs))
	}

	if clusters == nil {
		return clusterIDsByName, fmt.Errorf("No clusters found")
	}

	for _, cluster := range *clusters {
		clusterIDsByName[cluster.Name] = cluster.ID
	}
	return clusterIDsByName, nil
}

func fetchDeployments(client *compose.Client) (map[string]string, error) {
	deploymentIDsByName := make(map[string]string)
	deployments, errs := client.GetDeployments()
	if len(errs) != 0 {
		return deploymentIDsByName, fmt.Errorf("Failed to get deployments:\n%s",
			errsOut(errs))
	}

	if deployments == nil {
		// This is not necessarily an error.
		return deploymentIDsByName, nil
	}

	for _, deployment := range *deployments {
		deploymentIDsByName[deployment.Name] = deployment.ID
	}
	return deploymentIDsByName, nil
}

func errsOut(errs []error) string {
	var buf bytes.Buffer
	for _, err := range errs {
		if _, wErr := buf.WriteString(err.Error()); wErr != nil {
			panic(wErr)
		}
		if _, wErr := buf.WriteString("\n"); wErr != nil {
			panic(wErr)
		}
	}
	return buf.String()
}
<|endoftext|>


<commit_before>package connection

import (
	"net"
)

type Connection struct {
	Registered        bool
	Alias             string
	IncommingMessages chan []byte
	ReaderListening   bool
	OutgoingMessages  chan []byte
	WriterListening   bool
	Socket            net.Conn
}

func NewConnection(conn net.Conn) Connection {
	return Connection{
		Registered:        false,
		ReaderListening:   false,
		WriterListening:   false,
		IncommingMessages: make(chan []byte, 100),
		OutgoingMessages:  make(chan []byte, 100),
		Socket:            conn,
	}
}
<commit_msg>Connection Management Functionality<commit_after>package connection

import (
	"github.com/satori/go.uuid"
	"net"
)

var AllConnections []*Connection = make([]*Connection, 0, 100)

type Connection struct {
	Registered        bool
	Alias             string
	IncommingMessages chan []byte
	ReaderListening   bool
	OutgoingMessages  chan []byte
	WriterListening   bool
	Socket            net.Conn
}

func NewConnection(conn net.Conn) *Connection {
	newConn := &Connection{
		Alias:             uuid.NewV4().String(),
		Registered:        false,
		ReaderListening:   false,
		WriterListening:   false,
		IncommingMessages: make(chan []byte, 100),
		OutgoingMessages:  make(chan []byte, 100),
		Socket:            conn,
	}

	AddConnection(newConn)
	return newConn
}

func AddConnection(conn *Connection) {
	AllConnections = append(AllConnections, conn)
}

func RemoveConnection(conn *Connection) {
	var i int
	for i = 0; i < len(AllConnections); i++ {
		if AllConnections[i].Alias == conn.Alias {
			AllConnections = append(AllConnections[:i], AllConnections[i+1:]...)
			return
		}
	}
}
<|endoftext|>


<commit_before>package consensus

import (
	"errors"
	"fmt"
	. "github.com/divtxt/raft"
	config "github.com/divtxt/raft/config"
	consensus_state "github.com/divtxt/raft/consensus/state"
	util "github.com/divtxt/raft/util"
	"time"
)

type PassiveConsensusModule struct {
	// ===== the following fields meant to be immutable

	// -- External components
	RaftPersistentState RaftPersistentState
	LogRO               LogReadOnly
	_log                Log
	_changeListener     ChangeListener
	RpcSendOnly         RpcSendOnly

	// -- Config
	ClusterInfo              *config.ClusterInfo
	maxEntriesPerAppendEntry uint64

	// -- State - for all servers
	serverState ServerState

	// commitIndex is the index of highest log entry known to be committed
	// (initialized to 0, increases monotonically)
	_commitIndex           LogIndex
	ElectionTimeoutTracker *util.ElectionTimeoutTracker

	// -- State - for candidates only
	CandidateVolatileState *consensus_state.CandidateVolatileState

	// -- State - for leaders only
	LeaderVolatileState *consensus_state.LeaderVolatileState
}

func NewPassiveConsensusModule(
	raftPersistentState RaftPersistentState,
	log Log,
	changeListener ChangeListener,
	rpcSendOnly RpcSendOnly,
	clusterInfo *config.ClusterInfo,
	maxEntriesPerAppendEntry uint64,
	electionTimeoutLow time.Duration,
	now time.Time,
) (*PassiveConsensusModule, error) {
	// Param checks
	if raftPersistentState == nil {
		return nil, errors.New("'raftPersistentState' cannot be nil")
	}
	if log == nil {
		return nil, errors.New("'log' cannot be nil")
	}
	if rpcSendOnly == nil {
		return nil, errors.New("'rpcSendOnly' cannot be nil")
	}
	if clusterInfo == nil {
		return nil, errors.New("clusterInfo cannot be nil")
	}
	if electionTimeoutLow.Nanoseconds() <= 0 {
		return nil, errors.New("electionTimeoutLow must be greater than zero")
	}

	pcm := &PassiveConsensusModule{
		// -- External components
		raftPersistentState,
		log,
		log,
		changeListener,
		rpcSendOnly,

		// -- Config
		clusterInfo,
		maxEntriesPerAppendEntry,

		// -- State - for all servers
		// #5.2-p1s2: When servers start up, they begin as followers
		FOLLOWER,

		// -- State - for all servers
		// commitIndex is the index of highest log entry known to be committed
		// (initialized to 0, increases monotonically)
		0,
		util.NewElectionTimeoutTracker(electionTimeoutLow, now),

		// -- State - for candidates only
		nil,

		// -- State - for leaders only
		nil,
	}

	return pcm, nil
}

// Set the ChangeListener.
// Replaces the current ChangeListener.
func (cm *PassiveConsensusModule) SetChangeListener(changeListener ChangeListener) {
	cm._changeListener = changeListener
}

// Get the current server state.
// Validates the server state before returning.
func (cm *PassiveConsensusModule) GetServerState() ServerState {
	return cm.serverState
}

// Set the current server state.
// Validates the server state before setting.
func (cm *PassiveConsensusModule) setServerState(serverState ServerState) {
	if serverState != FOLLOWER && serverState != CANDIDATE && serverState != LEADER {
		panic(fmt.Sprintf("FATAL: unknown ServerState: %v", serverState))
	}
	cm.serverState = serverState
}

// Get the current commitIndex value.
func (cm *PassiveConsensusModule) GetCommitIndex() LogIndex {
	return cm._commitIndex
}

// Set the current commitIndex value.
// Checks that it is does not reduce.
func (cm *PassiveConsensusModule) setCommitIndex(commitIndex LogIndex) error {
	if commitIndex < cm._commitIndex {
		return fmt.Errorf(
			"setCommitIndex to %v < current commitIndex %v",
			commitIndex,
			cm._commitIndex,
		)
	}
	cm._commitIndex = commitIndex
	if cm._changeListener != nil {
		cm._changeListener.CommitIndexChanged(commitIndex)
	}
	return nil
}

// Append the given command as an entry in the log.
// #RFS-L2a: If command received from client: append entry to local log
//
// The command will be sent to Log.AppendEntry().
//
// Returns ErrNotLeader if not currently the leader.
func (cm *PassiveConsensusModule) AppendCommand(command Command) (LogIndex, error) {
	if cm.GetServerState() != LEADER {
		return 0, ErrNotLeader
	}

	termNo := cm.RaftPersistentState.GetCurrentTerm()
	logEntry := LogEntry{termNo, command}
	iole, err := cm._log.AppendEntry(logEntry)
	if err != nil {
		return 0, err
	}

	return iole, nil
}

// Iterate
func (cm *PassiveConsensusModule) Tick(now time.Time) error {
	serverState := cm.GetServerState()
	switch serverState {
	case FOLLOWER:
		// #RFS-F2: If election timeout elapses without receiving
		// AppendEntries RPC from current leader or granting vote
		// to candidate: convert to candidate
		// #5.2-p1s5: If a follower receives no communication over a period
		// of time called the election timeout, then it assumes there is no
		// viable leader and begins an election to choose a new leader.
		fallthrough
	case CANDIDATE:
		// #RFS-C4: If election timeout elapses: start new election
		// #5.2-p5s1: The third possible outcome is that a candidate neither
		// wins nor loses the election; ... votes could be split so that no
		// candidate obtains a majority.
		// #5.2-p5s2: When this happens, each candidate will time out and
		// start a new election by incrementing its term and initiating
		// another round of RequestVote RPCs.
		if cm.ElectionTimeoutTracker.ElectionTimeoutHasOccurred(now) {
			err := cm.becomeCandidateAndBeginElection(now)
			if err != nil {
				return err
			}
			// *** SOLO ***
			// Single node cluster wins election immediately since it has all the votes
			// But don't skip the election process, mainly since it increases current term!
			if cm.ClusterInfo.GetClusterSize() == 1 {
				err := cm.becomeLeader()
				if err != nil {
					return err
				}
			}
		}
	case LEADER:
		// #RFS-L4: If there exists an N such that N > commitIndex, a majority
		// of matchIndex[i] >= N, and log[N].term == currentTerm:
		// set commitIndex = N (#5.3, #5.4)
		err := cm.advanceCommitIndexIfPossible()
		if err != nil {
			return err
		}
		// #RFS-L3.0: If last log index >= nextIndex for a follower: send
		// AppendEntries RPC with log entries starting at nextIndex
		err = cm.sendAppendEntriesToAllPeers(false)
		if err != nil {
			return err
		}
	}
	return nil
}

func (cm *PassiveConsensusModule) becomeCandidateAndBeginElection(now time.Time) error {
	// #RFS-C1: On conversion to candidate, start election:
	// Increment currentTerm; Vote for self; Send RequestVote RPCs
	// to all other servers; Reset election timer
	// #5.2-p2s1: To begin an election, a follower increments its
	// current term and transitions to candidate state.
	newTerm := cm.RaftPersistentState.GetCurrentTerm() + 1
	err := cm.RaftPersistentState.SetCurrentTerm(newTerm)
	if err != nil {
		return err
	}
	cm.CandidateVolatileState, err = consensus_state.NewCandidateVolatileState(cm.ClusterInfo)
	if err != nil {
		return err
	}
	cm.setServerState(CANDIDATE)
	// #5.2-p2s2: It then votes for itself and issues RequestVote RPCs
	// in parallel to each of the other servers in the cluster.
	err = cm.RaftPersistentState.SetVotedFor(cm.ClusterInfo.GetThisServerId())
	if err != nil {
		return err
	}
	lastLogIndex, lastLogTerm, err := GetIndexAndTermOfLastEntry(cm.LogRO)
	if err != nil {
		return err
	}
	err = cm.ClusterInfo.ForEachPeer(
		func(serverId ServerId) error {
			rpcRequestVote := &RpcRequestVote{newTerm, lastLogIndex, lastLogTerm}
			cm.RpcSendOnly.SendOnlyRpcRequestVoteAsync(serverId, rpcRequestVote)
			return nil
		},
	)
	if err != nil {
		return err
	}
	// Reset election timeout!
	cm.ElectionTimeoutTracker.ChooseNewRandomElectionTimeoutAndTouch(now)
	return nil
}

func (cm *PassiveConsensusModule) becomeLeader() error {
	iole, err := cm.LogRO.GetIndexOfLastEntry()
	if err != nil {
		return err
	}
	cm.LeaderVolatileState, err = consensus_state.NewLeaderVolatileState(cm.ClusterInfo, iole)
	if err != nil {
		return err
	}
	cm.setServerState(LEADER)
	// #RFS-L1a: Upon election: send initial empty AppendEntries RPCs (heartbeat)
	// to each server;
	err = cm.sendAppendEntriesToAllPeers(true)
	if err != nil {
		return err
	}
	return nil
}

func (cm *PassiveConsensusModule) becomeFollowerWithTerm(newTerm TermNo) error {
	cm.setServerState(FOLLOWER)
	err := cm.RaftPersistentState.SetCurrentTerm(newTerm)
	if err != nil {
		return err
	}
	return nil
}

// -- leader code

func (cm *PassiveConsensusModule) sendAppendEntriesToAllPeers(empty bool) error {
	return cm.ClusterInfo.ForEachPeer(
		func(serverId ServerId) error {
			return cm.sendAppendEntriesToPeer(serverId, empty)
		},
	)
}

func (cm *PassiveConsensusModule) sendAppendEntriesToPeer(peerId ServerId, empty bool) error {
	serverTerm := cm.RaftPersistentState.GetCurrentTerm()
	//
	peerNextIndex, err := cm.LeaderVolatileState.GetNextIndex(peerId)
	if err != nil {
		return err
	}
	peerLastLogIndex := peerNextIndex - 1
	var peerLastLogTerm TermNo
	if peerLastLogIndex == 0 {
		peerLastLogTerm = 0
	} else {
		var err error
		peerLastLogTerm, err = cm.LogRO.GetTermAtIndex(peerLastLogIndex)
		if err != nil {
			return err
		}
	}
	//
	var entriesToSend []LogEntry
	if empty {
		entriesToSend = []LogEntry{}
	} else {
		var err error
		entriesToSend, err = cm.LogRO.GetEntriesAfterIndex(peerLastLogIndex, cm.maxEntriesPerAppendEntry)
		if err != nil {
			return err
		}
	}
	//
	rpcAppendEntries := &RpcAppendEntries{
		serverTerm,
		peerLastLogIndex,
		peerLastLogTerm,
		entriesToSend,
		cm.GetCommitIndex(),
	}
	cm.RpcSendOnly.SendOnlyRpcAppendEntriesAsync(peerId, rpcAppendEntries)
	return nil
}

// #RFS-L4: If there exists an N such that N > commitIndex, a majority
// of matchIndex[i] >= N, and log[N].term == currentTerm:
// set commitIndex = N (#5.3, #5.4)
func (cm *PassiveConsensusModule) advanceCommitIndexIfPossible() error {
	newerCommitIndex, err := consensus_state.FindNewerCommitIndex(
		cm.ClusterInfo,
		cm.LeaderVolatileState,
		cm.LogRO,
		cm.RaftPersistentState.GetCurrentTerm(),
		cm.GetCommitIndex(),
	)
	if err != nil {
		return err
	}
	if newerCommitIndex != 0 && newerCommitIndex > cm.GetCommitIndex() {
		err = cm.setCommitIndex(newerCommitIndex)
		if err != nil {
			return err
		}
	}
	return nil
}

// Wrapper for the call to Log.SetEntriesAfterIndex()
func (cm *PassiveConsensusModule) setEntriesAfterIndex(li LogIndex, entries []LogEntry) error {
	// Check that we're not trying to rewind past commitIndex
	if li < cm._commitIndex {
		return fmt.Errorf("FATAL: setEntriesAfterIndex(%d, ...) but commitIndex=%d", li, cm._commitIndex)
	}
	return cm._log.SetEntriesAfterIndex(li, entries)
}

// -- rpc bridging things

// This is an internal equivalent to RpcService without the reply part.
type RpcSendOnly interface {
	SendOnlyRpcAppendEntriesAsync(toServer ServerId, rpc *RpcAppendEntries)
	SendOnlyRpcRequestVoteAsync(toServer ServerId, rpc *RpcRequestVote)
}
<commit_msg>PaiveConsensusModule no longer needs SetChangeListener<commit_after>package consensus

import (
	"errors"
	"fmt"
	"time"

	. "github.com/divtxt/raft"
	config "github.com/divtxt/raft/config"
	consensus_state "github.com/divtxt/raft/consensus/state"
	util "github.com/divtxt/raft/util"
)

type PassiveConsensusModule struct {
	// ===== the following fields meant to be immutable

	// -- External components
	RaftPersistentState RaftPersistentState
	LogRO               LogReadOnly
	_log                Log
	_changeListener     ChangeListener
	RpcSendOnly         RpcSendOnly

	// -- Config
	ClusterInfo              *config.ClusterInfo
	maxEntriesPerAppendEntry uint64

	// -- State - for all servers
	serverState ServerState

	// commitIndex is the index of highest log entry known to be committed
	// (initialized to 0, increases monotonically)
	_commitIndex           LogIndex
	ElectionTimeoutTracker *util.ElectionTimeoutTracker

	// -- State - for candidates only
	CandidateVolatileState *consensus_state.CandidateVolatileState

	// -- State - for leaders only
	LeaderVolatileState *consensus_state.LeaderVolatileState
}

func NewPassiveConsensusModule(
	raftPersistentState RaftPersistentState,
	log Log,
	changeListener ChangeListener,
	rpcSendOnly RpcSendOnly,
	clusterInfo *config.ClusterInfo,
	maxEntriesPerAppendEntry uint64,
	electionTimeoutLow time.Duration,
	now time.Time,
) (*PassiveConsensusModule, error) {
	// Param checks
	if raftPersistentState == nil {
		return nil, errors.New("'raftPersistentState' cannot be nil")
	}
	if log == nil {
		return nil, errors.New("'log' cannot be nil")
	}
	if changeListener == nil {
		return nil, errors.New("'changeListener' cannot be nil")
	}
	if rpcSendOnly == nil {
		return nil, errors.New("'rpcSendOnly' cannot be nil")
	}
	if clusterInfo == nil {
		return nil, errors.New("clusterInfo cannot be nil")
	}
	if electionTimeoutLow.Nanoseconds() <= 0 {
		return nil, errors.New("electionTimeoutLow must be greater than zero")
	}

	pcm := &PassiveConsensusModule{
		// -- External components
		raftPersistentState,
		log,
		log,
		changeListener,
		rpcSendOnly,

		// -- Config
		clusterInfo,
		maxEntriesPerAppendEntry,

		// -- State - for all servers
		// #5.2-p1s2: When servers start up, they begin as followers
		FOLLOWER,

		// -- State - for all servers
		// commitIndex is the index of highest log entry known to be committed
		// (initialized to 0, increases monotonically)
		0,
		util.NewElectionTimeoutTracker(electionTimeoutLow, now),

		// -- State - for candidates only
		nil,

		// -- State - for leaders only
		nil,
	}

	return pcm, nil
}

// Get the current server state.
// Validates the server state before returning.
func (cm *PassiveConsensusModule) GetServerState() ServerState {
	return cm.serverState
}

// Set the current server state.
// Validates the server state before setting.
func (cm *PassiveConsensusModule) setServerState(serverState ServerState) {
	if serverState != FOLLOWER && serverState != CANDIDATE && serverState != LEADER {
		panic(fmt.Sprintf("FATAL: unknown ServerState: %v", serverState))
	}
	cm.serverState = serverState
}

// Get the current commitIndex value.
func (cm *PassiveConsensusModule) GetCommitIndex() LogIndex {
	return cm._commitIndex
}

// Set the current commitIndex value.
// Checks that it is does not reduce.
func (cm *PassiveConsensusModule) setCommitIndex(commitIndex LogIndex) error {
	if commitIndex < cm._commitIndex {
		return fmt.Errorf(
			"setCommitIndex to %v < current commitIndex %v",
			commitIndex,
			cm._commitIndex,
		)
	}
	cm._commitIndex = commitIndex
	cm._changeListener.CommitIndexChanged(commitIndex)
	return nil
}

// Append the given command as an entry in the log.
// #RFS-L2a: If command received from client: append entry to local log
//
// The command will be sent to Log.AppendEntry().
//
// Returns ErrNotLeader if not currently the leader.
func (cm *PassiveConsensusModule) AppendCommand(command Command) (LogIndex, error) {
	if cm.GetServerState() != LEADER {
		return 0, ErrNotLeader
	}

	termNo := cm.RaftPersistentState.GetCurrentTerm()
	logEntry := LogEntry{termNo, command}
	iole, err := cm._log.AppendEntry(logEntry)
	if err != nil {
		return 0, err
	}

	return iole, nil
}

// Iterate
func (cm *PassiveConsensusModule) Tick(now time.Time) error {
	serverState := cm.GetServerState()
	switch serverState {
	case FOLLOWER:
		// #RFS-F2: If election timeout elapses without receiving
		// AppendEntries RPC from current leader or granting vote
		// to candidate: convert to candidate
		// #5.2-p1s5: If a follower receives no communication over a period
		// of time called the election timeout, then it assumes there is no
		// viable leader and begins an election to choose a new leader.
		fallthrough
	case CANDIDATE:
		// #RFS-C4: If election timeout elapses: start new election
		// #5.2-p5s1: The third possible outcome is that a candidate neither
		// wins nor loses the election; ... votes could be split so that no
		// candidate obtains a majority.
		// #5.2-p5s2: When this happens, each candidate will time out and
		// start a new election by incrementing its term and initiating
		// another round of RequestVote RPCs.
		if cm.ElectionTimeoutTracker.ElectionTimeoutHasOccurred(now) {
			err := cm.becomeCandidateAndBeginElection(now)
			if err != nil {
				return err
			}
			// *** SOLO ***
			// Single node cluster wins election immediately since it has all the votes
			// But don't skip the election process, mainly since it increases current term!
			if cm.ClusterInfo.GetClusterSize() == 1 {
				err := cm.becomeLeader()
				if err != nil {
					return err
				}
			}
		}
	case LEADER:
		// #RFS-L4: If there exists an N such that N > commitIndex, a majority
		// of matchIndex[i] >= N, and log[N].term == currentTerm:
		// set commitIndex = N (#5.3, #5.4)
		err := cm.advanceCommitIndexIfPossible()
		if err != nil {
			return err
		}
		// #RFS-L3.0: If last log index >= nextIndex for a follower: send
		// AppendEntries RPC with log entries starting at nextIndex
		err = cm.sendAppendEntriesToAllPeers(false)
		if err != nil {
			return err
		}
	}
	return nil
}

func (cm *PassiveConsensusModule) becomeCandidateAndBeginElection(now time.Time) error {
	// #RFS-C1: On conversion to candidate, start election:
	// Increment currentTerm; Vote for self; Send RequestVote RPCs
	// to all other servers; Reset election timer
	// #5.2-p2s1: To begin an election, a follower increments its
	// current term and transitions to candidate state.
	newTerm := cm.RaftPersistentState.GetCurrentTerm() + 1
	err := cm.RaftPersistentState.SetCurrentTerm(newTerm)
	if err != nil {
		return err
	}
	cm.CandidateVolatileState, err = consensus_state.NewCandidateVolatileState(cm.ClusterInfo)
	if err != nil {
		return err
	}
	cm.setServerState(CANDIDATE)
	// #5.2-p2s2: It then votes for itself and issues RequestVote RPCs
	// in parallel to each of the other servers in the cluster.
	err = cm.RaftPersistentState.SetVotedFor(cm.ClusterInfo.GetThisServerId())
	if err != nil {
		return err
	}
	lastLogIndex, lastLogTerm, err := GetIndexAndTermOfLastEntry(cm.LogRO)
	if err != nil {
		return err
	}
	err = cm.ClusterInfo.ForEachPeer(
		func(serverId ServerId) error {
			rpcRequestVote := &RpcRequestVote{newTerm, lastLogIndex, lastLogTerm}
			cm.RpcSendOnly.SendOnlyRpcRequestVoteAsync(serverId, rpcRequestVote)
			return nil
		},
	)
	if err != nil {
		return err
	}
	// Reset election timeout!
	cm.ElectionTimeoutTracker.ChooseNewRandomElectionTimeoutAndTouch(now)
	return nil
}

func (cm *PassiveConsensusModule) becomeLeader() error {
	iole, err := cm.LogRO.GetIndexOfLastEntry()
	if err != nil {
		return err
	}
	cm.LeaderVolatileState, err = consensus_state.NewLeaderVolatileState(cm.ClusterInfo, iole)
	if err != nil {
		return err
	}
	cm.setServerState(LEADER)
	// #RFS-L1a: Upon election: send initial empty AppendEntries RPCs (heartbeat)
	// to each server;
	err = cm.sendAppendEntriesToAllPeers(true)
	if err != nil {
		return err
	}
	return nil
}

func (cm *PassiveConsensusModule) becomeFollowerWithTerm(newTerm TermNo) error {
	cm.setServerState(FOLLOWER)
	err := cm.RaftPersistentState.SetCurrentTerm(newTerm)
	if err != nil {
		return err
	}
	return nil
}

// -- leader code

func (cm *PassiveConsensusModule) sendAppendEntriesToAllPeers(empty bool) error {
	return cm.ClusterInfo.ForEachPeer(
		func(serverId ServerId) error {
			return cm.sendAppendEntriesToPeer(serverId, empty)
		},
	)
}

func (cm *PassiveConsensusModule) sendAppendEntriesToPeer(peerId ServerId, empty bool) error {
	serverTerm := cm.RaftPersistentState.GetCurrentTerm()
	//
	peerNextIndex, err := cm.LeaderVolatileState.GetNextIndex(peerId)
	if err != nil {
		return err
	}
	peerLastLogIndex := peerNextIndex - 1
	var peerLastLogTerm TermNo
	if peerLastLogIndex == 0 {
		peerLastLogTerm = 0
	} else {
		var err error
		peerLastLogTerm, err = cm.LogRO.GetTermAtIndex(peerLastLogIndex)
		if err != nil {
			return err
		}
	}
	//
	var entriesToSend []LogEntry
	if empty {
		entriesToSend = []LogEntry{}
	} else {
		var err error
		entriesToSend, err = cm.LogRO.GetEntriesAfterIndex(peerLastLogIndex, cm.maxEntriesPerAppendEntry)
		if err != nil {
			return err
		}
	}
	//
	rpcAppendEntries := &RpcAppendEntries{
		serverTerm,
		peerLastLogIndex,
		peerLastLogTerm,
		entriesToSend,
		cm.GetCommitIndex(),
	}
	cm.RpcSendOnly.SendOnlyRpcAppendEntriesAsync(peerId, rpcAppendEntries)
	return nil
}

// #RFS-L4: If there exists an N such that N > commitIndex, a majority
// of matchIndex[i] >= N, and log[N].term == currentTerm:
// set commitIndex = N (#5.3, #5.4)
func (cm *PassiveConsensusModule) advanceCommitIndexIfPossible() error {
	newerCommitIndex, err := consensus_state.FindNewerCommitIndex(
		cm.ClusterInfo,
		cm.LeaderVolatileState,
		cm.LogRO,
		cm.RaftPersistentState.GetCurrentTerm(),
		cm.GetCommitIndex(),
	)
	if err != nil {
		return err
	}
	if newerCommitIndex != 0 && newerCommitIndex > cm.GetCommitIndex() {
		err = cm.setCommitIndex(newerCommitIndex)
		if err != nil {
			return err
		}
	}
	return nil
}

// Wrapper for the call to Log.SetEntriesAfterIndex()
func (cm *PassiveConsensusModule) setEntriesAfterIndex(li LogIndex, entries []LogEntry) error {
	// Check that we're not trying to rewind past commitIndex
	if li < cm._commitIndex {
		return fmt.Errorf("FATAL: setEntriesAfterIndex(%d, ...) but commitIndex=%d", li, cm._commitIndex)
	}
	return cm._log.SetEntriesAfterIndex(li, entries)
}

// -- rpc bridging things

// This is an internal equivalent to RpcService without the reply part.
type RpcSendOnly interface {
	SendOnlyRpcAppendEntriesAsync(toServer ServerId, rpc *RpcAppendEntries)
	SendOnlyRpcRequestVoteAsync(toServer ServerId, rpc *RpcRequestVote)
}
<|endoftext|>