Actually update and draw things asynchronously too, but wait for responses.
parent
b148699a58
commit
b3836dc411
|
@ -0,0 +1,27 @@
|
|||
package channels
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// BlockingFunction will wait for the function to be completed
|
||||
// or exit if the context is done.
|
||||
func BlockingFunction(ctx context.Context, function func() error) error {
|
||||
var err error
|
||||
|
||||
finished := make(chan bool)
|
||||
go func() {
|
||||
err = function()
|
||||
finished <- true
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-finished:
|
||||
// Completed
|
||||
case <-ctx.Done():
|
||||
// Context closed (e.g. timed out or ctrl+c)
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
|
@ -5,25 +5,12 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
// RunWithTimeout will run a function in a goroutine with a new timeout context.
|
||||
// If the context times out, then we exit the goroutine the return the context error.
|
||||
func RunWithTimeout(timeout time.Duration, function func() error) error {
|
||||
var err error
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
// Launch a goroutine with a channel
|
||||
finished := make(chan bool)
|
||||
go func() {
|
||||
err = function()
|
||||
finished <- true
|
||||
}()
|
||||
|
||||
// Then wait for completion or timeout
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// Timed out
|
||||
return ctx.Err()
|
||||
case <-finished:
|
||||
// Completed
|
||||
}
|
||||
err := BlockingFunction(ctx, function)
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package entity
|
||||
package animation
|
||||
|
||||
import (
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
|
@ -44,6 +44,10 @@ func (e *entityAnimation) Draw(frame int32, windowPosition *sdl.Point) error {
|
|||
return e.spriteAnimation.Draw(frame, windowPosition, e.angle, e.center, e.flip)
|
||||
}
|
||||
|
||||
func (e *entityAnimation) GetSpeed() int32 {
|
||||
return e.speed
|
||||
}
|
||||
|
||||
func DefineAnimations() {
|
||||
DefinePenguinAnimations()
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
package entity
|
||||
package animation
|
||||
|
||||
import (
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/game/sprite"
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
)
|
||||
|
||||
var penguinAnimations map[int]*entityAnimation
|
||||
var PenguinAnimations map[int]*entityAnimation
|
||||
|
||||
const (
|
||||
PENGUIN_WALK_RIGHT int = iota
|
||||
|
@ -28,7 +28,7 @@ func DefinePenguinAnimations() {
|
|||
)
|
||||
|
||||
dimensions = sdl.Point{X: 32, Y: 32}
|
||||
penguinAnimations = make(map[int]*entityAnimation)
|
||||
PenguinAnimations = make(map[int]*entityAnimation)
|
||||
|
||||
// Walking Right is in the spritesheet.
|
||||
speed = 5
|
||||
|
@ -36,14 +36,14 @@ func DefinePenguinAnimations() {
|
|||
length = 4
|
||||
center = nil // center is for rotation, nil will default to w/2 h/2
|
||||
walkRight := sprite.NewAnimation(filename, dimensions, offset, length)
|
||||
penguinAnimations[PENGUIN_WALK_RIGHT] = NewEntityAnimation(walkRight, speed, length, 0, center, sdl.FLIP_NONE)
|
||||
PenguinAnimations[PENGUIN_WALK_RIGHT] = NewEntityAnimation(walkRight, speed, length, 0, center, sdl.FLIP_NONE)
|
||||
|
||||
// Walking Left is just that flipped.
|
||||
penguinAnimations[PENGUIN_WALK_LEFT] = NewEntityAnimation(walkRight, speed, length, 0, center, sdl.FLIP_HORIZONTAL)
|
||||
PenguinAnimations[PENGUIN_WALK_LEFT] = NewEntityAnimation(walkRight, speed, length, 0, center, sdl.FLIP_HORIZONTAL)
|
||||
|
||||
// Stationary is just the first frame.
|
||||
length = 1
|
||||
stationaryRight := sprite.NewAnimation(filename, dimensions, offset, length)
|
||||
penguinAnimations[PENGUIN_STATIONARY_RIGHT] = NewEntityAnimation(stationaryRight, speed, length, 0, center, sdl.FLIP_NONE)
|
||||
penguinAnimations[PENGUIN_STATIONARY_LEFT] = NewEntityAnimation(stationaryRight, speed, length, 0, center, sdl.FLIP_HORIZONTAL)
|
||||
PenguinAnimations[PENGUIN_STATIONARY_RIGHT] = NewEntityAnimation(stationaryRight, speed, length, 0, center, sdl.FLIP_NONE)
|
||||
PenguinAnimations[PENGUIN_STATIONARY_LEFT] = NewEntityAnimation(stationaryRight, speed, length, 0, center, sdl.FLIP_HORIZONTAL)
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
package entity
|
||||
package command
|
||||
|
||||
// List of keys supported by all entities
|
||||
const (
|
||||
COMMAND_DRAW int = iota
|
||||
COMMAND_UPDATE
|
||||
COMMAND_MOVE_X
|
||||
COMMAND_MOVE_Y
|
||||
COMMAND_SET_SPEED
|
||||
MOVE_X int = iota
|
||||
MOVE_Y
|
||||
SET_SPEED
|
||||
SET_POSITION
|
||||
SET_ANIMATION
|
||||
)
|
||||
|
||||
type EntityCommand struct {
|
|
@ -0,0 +1,170 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/channels"
|
||||
)
|
||||
|
||||
type Entity interface {
|
||||
Draw() error
|
||||
Update() error
|
||||
MoveX(x float64)
|
||||
MoveY(y float64)
|
||||
SetSpeed(s float64)
|
||||
}
|
||||
|
||||
type CommandHandler struct {
|
||||
ctx context.Context
|
||||
timeout time.Duration
|
||||
entity Entity
|
||||
commandChan chan EntityCommand
|
||||
drawRequestChan chan bool
|
||||
drawResponseChan chan bool
|
||||
updateRequestChan chan bool
|
||||
updateResponseChan chan bool
|
||||
}
|
||||
|
||||
func NewCommandHandler(ctx context.Context, entity Entity) *CommandHandler {
|
||||
commandChan := make(chan EntityCommand, 10)
|
||||
drawRequestChan := make(chan bool)
|
||||
drawResponseChan := make(chan bool)
|
||||
updateRequestChan := make(chan bool)
|
||||
updateResponseChan := make(chan bool)
|
||||
|
||||
defaultTimeout := time.Second
|
||||
return &CommandHandler{
|
||||
ctx: ctx,
|
||||
timeout: defaultTimeout,
|
||||
entity: entity,
|
||||
commandChan: commandChan,
|
||||
drawRequestChan: drawRequestChan,
|
||||
drawResponseChan: drawResponseChan,
|
||||
updateRequestChan: updateRequestChan,
|
||||
updateResponseChan: updateResponseChan,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommandHandler) closeResponses() {
|
||||
close(c.drawResponseChan)
|
||||
close(c.updateResponseChan)
|
||||
}
|
||||
|
||||
func (c *CommandHandler) CloseRequests() {
|
||||
close(c.drawRequestChan)
|
||||
close(c.updateRequestChan)
|
||||
close(c.commandChan)
|
||||
}
|
||||
|
||||
func (c *CommandHandler) CommandRequest() chan EntityCommand {
|
||||
return c.commandChan
|
||||
}
|
||||
|
||||
func (c *CommandHandler) DrawRequest() chan bool {
|
||||
return c.drawRequestChan
|
||||
}
|
||||
|
||||
func (c *CommandHandler) DrawResponse() chan bool {
|
||||
return c.drawResponseChan
|
||||
}
|
||||
|
||||
func (c *CommandHandler) UpdateRequest() chan bool {
|
||||
return c.updateRequestChan
|
||||
}
|
||||
|
||||
func (c *CommandHandler) UpdateResponse() chan bool {
|
||||
return c.updateResponseChan
|
||||
}
|
||||
|
||||
func (c *CommandHandler) Run() {
|
||||
defer c.closeResponses()
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
|
||||
running := true
|
||||
for running {
|
||||
select {
|
||||
case <-c.drawRequestChan:
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
c.DrawWithTimeout()
|
||||
c.drawResponseChan <- true
|
||||
}()
|
||||
case <-c.updateRequestChan:
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
c.UpdateWithTimeout()
|
||||
c.updateResponseChan <- true
|
||||
}()
|
||||
case cmd := <-c.commandChan:
|
||||
wg.Add(1)
|
||||
go func(cmd EntityCommand) {
|
||||
defer wg.Done()
|
||||
c.HandleWithTimeout(cmd)
|
||||
}(cmd)
|
||||
case <-c.ctx.Done():
|
||||
// Graceful shutdown
|
||||
running = false
|
||||
}
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// Finish up anything in the queues
|
||||
for cmd := range c.commandChan {
|
||||
c.HandleWithTimeout(cmd)
|
||||
}
|
||||
for range c.drawRequestChan {
|
||||
c.UpdateWithTimeout()
|
||||
}
|
||||
for range c.drawRequestChan {
|
||||
c.DrawWithTimeout()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommandHandler) HandleWithTimeout(cmd EntityCommand) {
|
||||
err := channels.RunWithTimeout(c.timeout, func() error {
|
||||
return c.Handle(cmd)
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("handle with timeout error: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommandHandler) Handle(cmd EntityCommand) error {
|
||||
switch cmd.key {
|
||||
case MOVE_X:
|
||||
c.entity.MoveX(cmd.value)
|
||||
case MOVE_Y:
|
||||
c.entity.MoveY(cmd.value)
|
||||
case SET_SPEED:
|
||||
c.entity.SetSpeed(cmd.value)
|
||||
default:
|
||||
log.Printf("unknown entity command: %v", cmd.key)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CommandHandler) DrawWithTimeout() {
|
||||
err := channels.RunWithTimeout(c.timeout, func() error {
|
||||
return c.entity.Draw()
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("draw with timeout error: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommandHandler) UpdateWithTimeout() {
|
||||
err := channels.RunWithTimeout(c.timeout, func() error {
|
||||
return c.entity.Update()
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("update with timeout error: %v\n", err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package types
|
||||
|
||||
import "github.com/veandco/go-sdl2/sdl"
|
||||
|
||||
type EntityAnimation interface {
|
||||
Draw(step int32, windowPosition *sdl.Point) error
|
||||
GetSpeed() int32
|
||||
}
|
|
@ -1,23 +1,20 @@
|
|||
package entity
|
||||
package types
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"math"
|
||||
"time"
|
||||
"sync"
|
||||
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/channels"
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/game/entity/animation"
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/vector"
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
)
|
||||
|
||||
type penguin struct {
|
||||
ctx context.Context
|
||||
timeout time.Duration
|
||||
commandChan chan EntityCommand
|
||||
mx sync.RWMutex
|
||||
|
||||
// Animation parameters
|
||||
currentAnimation *entityAnimation
|
||||
currentAnimation EntityAnimation
|
||||
animationStep int32
|
||||
facingRight bool
|
||||
updateAnimation bool
|
||||
|
@ -28,20 +25,13 @@ type penguin struct {
|
|||
speed float64
|
||||
}
|
||||
|
||||
func NewPenguin(ctx context.Context, renderer *sdl.Renderer) *penguin {
|
||||
commandChan := make(chan EntityCommand, 10)
|
||||
defaultTimeout := time.Second
|
||||
|
||||
func NewPenguin(renderer *sdl.Renderer) *penguin {
|
||||
position := vector.Vec2F{}
|
||||
direction := vector.Vec2F{}
|
||||
speed := 1.0
|
||||
|
||||
p := penguin{
|
||||
ctx: ctx,
|
||||
commandChan: commandChan,
|
||||
timeout: defaultTimeout,
|
||||
|
||||
currentAnimation: penguinAnimations[PENGUIN_DEFAULT],
|
||||
currentAnimation: animation.PenguinAnimations[animation.PENGUIN_DEFAULT],
|
||||
animationStep: 0,
|
||||
facingRight: true,
|
||||
|
||||
|
@ -54,8 +44,9 @@ func NewPenguin(ctx context.Context, renderer *sdl.Renderer) *penguin {
|
|||
}
|
||||
|
||||
func (p *penguin) Draw() error {
|
||||
step := p.animationStep / p.currentAnimation.speed
|
||||
step := p.animationStep / p.currentAnimation.GetSpeed()
|
||||
|
||||
// TODO
|
||||
//windowPosition := worldPosToWindowPos()
|
||||
windowPosition := &sdl.Point{
|
||||
X: int32(math.Round(p.worldPosition.X)),
|
||||
|
@ -77,11 +68,11 @@ func (p *penguin) SetPosition(vec *vector.Vec2F) {
|
|||
}
|
||||
|
||||
func (p *penguin) SetAnimation(id int) {
|
||||
a, exists := penguinAnimations[id]
|
||||
a, exists := animation.PenguinAnimations[id]
|
||||
|
||||
if !exists {
|
||||
log.Printf("animation does not exist: %v", id)
|
||||
a = penguinAnimations[PENGUIN_DEFAULT]
|
||||
a = animation.PenguinAnimations[animation.PENGUIN_DEFAULT]
|
||||
}
|
||||
|
||||
if a != p.currentAnimation {
|
||||
|
@ -92,12 +83,18 @@ func (p *penguin) SetAnimation(id int) {
|
|||
}
|
||||
|
||||
func (p *penguin) MoveX(x float64) {
|
||||
p.mx.Lock()
|
||||
defer p.mx.Unlock()
|
||||
|
||||
p.direction.X = x
|
||||
p.direction.Normalize()
|
||||
p.updateAnimation = true
|
||||
}
|
||||
|
||||
func (p *penguin) MoveY(y float64) {
|
||||
p.mx.Lock()
|
||||
defer p.mx.Unlock()
|
||||
|
||||
// (0,0) is the top left, so negative y moves up
|
||||
p.direction.Y = y
|
||||
p.direction.Normalize()
|
||||
|
@ -119,9 +116,9 @@ func (p *penguin) SetMoveAnimation() {
|
|||
if x == 0 && y == 0 {
|
||||
// Stationary
|
||||
if p.facingRight {
|
||||
p.SetAnimation(PENGUIN_STATIONARY_RIGHT)
|
||||
p.SetAnimation(animation.PENGUIN_STATIONARY_RIGHT)
|
||||
} else {
|
||||
p.SetAnimation(PENGUIN_STATIONARY_LEFT)
|
||||
p.SetAnimation(animation.PENGUIN_STATIONARY_LEFT)
|
||||
}
|
||||
} else {
|
||||
// Moving
|
||||
|
@ -136,9 +133,9 @@ func (p *penguin) SetMoveAnimation() {
|
|||
// if x == 0, stay facing whatever direction we were previously facing
|
||||
|
||||
if p.facingRight {
|
||||
p.SetAnimation(PENGUIN_WALK_RIGHT)
|
||||
p.SetAnimation(animation.PENGUIN_WALK_RIGHT)
|
||||
} else {
|
||||
p.SetAnimation(PENGUIN_WALK_LEFT)
|
||||
p.SetAnimation(animation.PENGUIN_WALK_LEFT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -157,62 +154,3 @@ func (p *penguin) Update() error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *penguin) GetCommandChan() chan EntityCommand {
|
||||
return p.commandChan
|
||||
}
|
||||
|
||||
func (p *penguin) Run() {
|
||||
running := true
|
||||
for running {
|
||||
select {
|
||||
case c := <-p.commandChan:
|
||||
go func(cmd EntityCommand) {
|
||||
p.HandleWithTimeout(cmd)
|
||||
}(c)
|
||||
case <-p.ctx.Done():
|
||||
// Graceful shutdown
|
||||
running = false
|
||||
// Finish up anything in the queue
|
||||
for c := range p.commandChan {
|
||||
p.HandleWithTimeout(c)
|
||||
}
|
||||
log.Printf("entity shutdown\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *penguin) HandleWithTimeout(c EntityCommand) {
|
||||
err := channels.RunWithTimeout(p.timeout, func() error {
|
||||
return p.Handle(c)
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("%v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *penguin) Handle(c EntityCommand) error {
|
||||
var err error
|
||||
switch c.key {
|
||||
case COMMAND_DRAW:
|
||||
err = p.Draw()
|
||||
if err != nil {
|
||||
log.Printf("error drawing entity: %v", err)
|
||||
}
|
||||
case COMMAND_UPDATE:
|
||||
err = p.Update()
|
||||
if err != nil {
|
||||
log.Printf("error updating entity: %v", err)
|
||||
}
|
||||
case COMMAND_MOVE_X:
|
||||
p.MoveX(c.value)
|
||||
case COMMAND_MOVE_Y:
|
||||
p.MoveY(c.value)
|
||||
case COMMAND_SET_SPEED:
|
||||
p.SetSpeed(c.value)
|
||||
default:
|
||||
log.Printf("unknown entity command: %v", c.key)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -4,22 +4,28 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/game/entity"
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/game/entity/player"
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/game/entity/animation"
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/game/entity/command"
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/game/entity/types"
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/game/player"
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/game/sprite"
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/vector"
|
||||
"gitea.wisellama.rocks/Wisellama/gosimpleconf"
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
)
|
||||
|
||||
type Entity interface {
|
||||
type EntityCmdHandler interface {
|
||||
Run()
|
||||
GetCommandChan() chan entity.EntityCommand
|
||||
Draw() error
|
||||
Update() error
|
||||
CloseRequests()
|
||||
CommandRequest() chan command.EntityCommand
|
||||
DrawRequest() chan bool
|
||||
DrawResponse() chan bool
|
||||
UpdateRequest() chan bool
|
||||
UpdateResponse() chan bool
|
||||
}
|
||||
|
||||
// Run is the main function to start the game.
|
||||
|
@ -73,35 +79,41 @@ func Run(ctx context.Context, configMap gosimpleconf.ConfigMap) error {
|
|||
}
|
||||
defer sprite.CleanupSpriteCache()
|
||||
|
||||
entity.DefinePenguinAnimations()
|
||||
animation.DefineAnimations()
|
||||
|
||||
// Done with main setup, now moving on to creating specific entities
|
||||
|
||||
entityList := make([]Entity, 0)
|
||||
entityList := make([]EntityCmdHandler, 0)
|
||||
wg := sync.WaitGroup{}
|
||||
|
||||
// Setup Player 1
|
||||
// Let them control a penguin to start with
|
||||
player1 := player.NewPlayer(ctx)
|
||||
penguin := entity.NewPenguin(ctx, renderer)
|
||||
penguin.SetSpeed(2.0)
|
||||
entityList = append(entityList, penguin)
|
||||
player1.SetEntityChan(penguin.GetCommandChan())
|
||||
penguinEntity := types.NewPenguin(renderer)
|
||||
penguinCmdHandler := command.NewCommandHandler(ctx, penguinEntity)
|
||||
penguinEntity.SetSpeed(2.0)
|
||||
entityList = append(entityList, penguinCmdHandler)
|
||||
player1.SetEntityChan(penguinCmdHandler.CommandRequest())
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
penguin.Run()
|
||||
penguinCmdHandler.Run()
|
||||
}()
|
||||
|
||||
p2 := entity.NewPenguin(ctx, renderer)
|
||||
p2.SetPosition(&vector.Vec2F{X: 100, Y: 100})
|
||||
p2.SetAnimation(entity.PENGUIN_WALK_LEFT)
|
||||
entityList = append(entityList, p2)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
p2.Run()
|
||||
}()
|
||||
for i := 0; i < 10; i++ {
|
||||
entity := types.NewPenguin(renderer)
|
||||
entityCmd := command.NewCommandHandler(ctx, entity)
|
||||
randomPos := vector.Vec2F{X: rand.Float64() * 500, Y: rand.Float64() * 500}
|
||||
entity.SetPosition(&randomPos)
|
||||
entity.SetAnimation(animation.PENGUIN_WALK_LEFT)
|
||||
entityList = append(entityList, entityCmd)
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
entityCmd.Run()
|
||||
}()
|
||||
}
|
||||
|
||||
// And now starting the main loop
|
||||
wg.Add(1)
|
||||
|
@ -112,15 +124,6 @@ func Run(ctx context.Context, configMap gosimpleconf.ConfigMap) error {
|
|||
|
||||
running := true
|
||||
for running {
|
||||
|
||||
// Allow us to exit gracefully if the context is done
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
running = false
|
||||
default:
|
||||
// Keep running
|
||||
}
|
||||
|
||||
// Poll for SDL events
|
||||
var event sdl.Event
|
||||
sdl.Do(func() {
|
||||
|
@ -145,6 +148,17 @@ func Run(ctx context.Context, configMap gosimpleconf.ConfigMap) error {
|
|||
}
|
||||
})
|
||||
|
||||
// Allow us to exit early if the context is done
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
running = false
|
||||
default:
|
||||
// Keep running
|
||||
}
|
||||
if !running {
|
||||
break
|
||||
}
|
||||
|
||||
// Background
|
||||
sdl.Do(func() {
|
||||
err = renderer.SetDrawColor(0, 120, 0, 255)
|
||||
|
@ -158,19 +172,16 @@ func Run(ctx context.Context, configMap gosimpleconf.ConfigMap) error {
|
|||
}
|
||||
})
|
||||
|
||||
// Everything else
|
||||
// Tell everything to Update and Draw
|
||||
for _, e := range entityList {
|
||||
//e.GetCommandChan() <- entity.NewEntityCommand(entity.COMMAND_UPDATE, 0)
|
||||
//e.GetCommandChan() <- entity.NewEntityCommand(entity.COMMAND_DRAW, 0)
|
||||
err = e.Update()
|
||||
if err != nil {
|
||||
log.Printf("error updating: %v", err)
|
||||
}
|
||||
e.UpdateRequest() <- true
|
||||
e.DrawRequest() <- true
|
||||
}
|
||||
|
||||
err = e.Draw()
|
||||
if err != nil {
|
||||
log.Printf("error drawing: %v", err)
|
||||
}
|
||||
// Wait for each entity to finish their Draw and Update commands before proceeding
|
||||
for _, e := range entityList {
|
||||
<-e.UpdateResponse()
|
||||
<-e.DrawResponse()
|
||||
}
|
||||
|
||||
// Draw
|
||||
|
@ -180,9 +191,10 @@ func Run(ctx context.Context, configMap gosimpleconf.ConfigMap) error {
|
|||
})
|
||||
}
|
||||
|
||||
close(player1.GetSdlEventsChan())
|
||||
player1.Cleanup()
|
||||
|
||||
for _, e := range entityList {
|
||||
close(e.GetCommandChan())
|
||||
e.CloseRequests()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
|
|
@ -3,10 +3,11 @@ package player
|
|||
import (
|
||||
"context"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/channels"
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/game/entity"
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/game/entity/command"
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
)
|
||||
|
||||
|
@ -15,41 +16,55 @@ import (
|
|||
// the viewport for the part of the screen to draw to (splitscreen support),
|
||||
// as well as the entity the player is currently controlling.
|
||||
type player struct {
|
||||
ctx context.Context
|
||||
timeout time.Duration
|
||||
sdlEventsChan chan sdl.KeyboardEvent
|
||||
mx sync.RWMutex
|
||||
ctx context.Context
|
||||
timeout time.Duration
|
||||
sdlKeyboardEventsChan chan sdl.KeyboardEvent
|
||||
|
||||
entityChan chan entity.EntityCommand
|
||||
entityChan chan command.EntityCommand
|
||||
keystates map[sdl.Keycode]bool
|
||||
}
|
||||
|
||||
func NewPlayer(ctx context.Context) *player {
|
||||
sdlEventsChan := make(chan sdl.KeyboardEvent, 10)
|
||||
sdlKeyboardEventsChan := make(chan sdl.KeyboardEvent, 10)
|
||||
defaultTimeout := time.Second
|
||||
|
||||
keystates := make(map[sdl.Keycode]bool)
|
||||
p := player{
|
||||
ctx: ctx,
|
||||
timeout: defaultTimeout,
|
||||
sdlEventsChan: sdlEventsChan,
|
||||
keystates: keystates,
|
||||
ctx: ctx,
|
||||
timeout: defaultTimeout,
|
||||
sdlKeyboardEventsChan: sdlKeyboardEventsChan,
|
||||
keystates: keystates,
|
||||
}
|
||||
return &p
|
||||
}
|
||||
|
||||
func (p *player) SetEntityChan(e chan entity.EntityCommand) {
|
||||
func (p *player) Cleanup() {
|
||||
p.mx.Lock()
|
||||
defer p.mx.Unlock()
|
||||
|
||||
close(p.sdlKeyboardEventsChan)
|
||||
}
|
||||
|
||||
func (p *player) SetEntityChan(e chan command.EntityCommand) {
|
||||
p.mx.Lock()
|
||||
defer p.mx.Unlock()
|
||||
|
||||
p.entityChan = e
|
||||
}
|
||||
|
||||
func (p *player) GetSdlEventsChan() chan sdl.KeyboardEvent {
|
||||
return p.sdlEventsChan
|
||||
p.mx.RLock()
|
||||
defer p.mx.RUnlock()
|
||||
|
||||
return p.sdlKeyboardEventsChan
|
||||
}
|
||||
|
||||
func (p *player) Run() {
|
||||
running := true
|
||||
for running {
|
||||
select {
|
||||
case e := <-p.sdlEventsChan:
|
||||
case e := <-p.sdlKeyboardEventsChan:
|
||||
go func(event sdl.KeyboardEvent) {
|
||||
p.HandleWithTimeout(event)
|
||||
}(e)
|
||||
|
@ -57,10 +72,9 @@ func (p *player) Run() {
|
|||
// Graceful shutdown
|
||||
running = false
|
||||
// Finish up anything in the queue
|
||||
for e := range p.sdlEventsChan {
|
||||
for e := range p.sdlKeyboardEventsChan {
|
||||
p.HandleWithTimeout(e)
|
||||
}
|
||||
log.Printf("player shutdown\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -75,7 +89,9 @@ func (p *player) HandleWithTimeout(event sdl.KeyboardEvent) {
|
|||
}
|
||||
|
||||
func (p *player) Handle(e sdl.KeyboardEvent) error {
|
||||
log.Printf("%v", e)
|
||||
p.mx.Lock()
|
||||
defer p.mx.Unlock()
|
||||
|
||||
// Key states (just set a boolean whether the key is actively being pressed)
|
||||
keystateChanged := false
|
||||
if e.Type == sdl.KEYDOWN {
|
||||
|
@ -97,27 +113,27 @@ func (p *player) Handle(e sdl.KeyboardEvent) error {
|
|||
|
||||
// Speed
|
||||
if p.keystates[sdl.K_LSHIFT] {
|
||||
p.entityChan <- entity.NewEntityCommand(entity.COMMAND_SET_SPEED, 4)
|
||||
p.entityChan <- command.NewEntityCommand(command.SET_SPEED, 4)
|
||||
} else {
|
||||
p.entityChan <- entity.NewEntityCommand(entity.COMMAND_SET_SPEED, 2)
|
||||
p.entityChan <- command.NewEntityCommand(command.SET_SPEED, 2)
|
||||
}
|
||||
|
||||
// Move X
|
||||
if p.keystates[sdl.K_d] {
|
||||
p.entityChan <- entity.NewEntityCommand(entity.COMMAND_MOVE_X, 1.0)
|
||||
p.entityChan <- command.NewEntityCommand(command.MOVE_X, 1.0)
|
||||
} else if p.keystates[sdl.K_a] {
|
||||
p.entityChan <- entity.NewEntityCommand(entity.COMMAND_MOVE_X, -1.0)
|
||||
p.entityChan <- command.NewEntityCommand(command.MOVE_X, -1.0)
|
||||
} else if !p.keystates[sdl.K_d] && !p.keystates[sdl.K_a] {
|
||||
p.entityChan <- entity.NewEntityCommand(entity.COMMAND_MOVE_X, 0.0)
|
||||
p.entityChan <- command.NewEntityCommand(command.MOVE_X, 0.0)
|
||||
}
|
||||
|
||||
// Move Y
|
||||
if p.keystates[sdl.K_w] {
|
||||
p.entityChan <- entity.NewEntityCommand(entity.COMMAND_MOVE_Y, -1.0)
|
||||
p.entityChan <- command.NewEntityCommand(command.MOVE_Y, -1.0)
|
||||
} else if p.keystates[sdl.K_s] {
|
||||
p.entityChan <- entity.NewEntityCommand(entity.COMMAND_MOVE_Y, 1.0)
|
||||
p.entityChan <- command.NewEntityCommand(command.MOVE_Y, 1.0)
|
||||
} else if !p.keystates[sdl.K_w] && !p.keystates[sdl.K_s] {
|
||||
p.entityChan <- entity.NewEntityCommand(entity.COMMAND_MOVE_Y, 0.0)
|
||||
p.entityChan <- command.NewEntityCommand(command.MOVE_Y, 0.0)
|
||||
}
|
||||
return nil
|
||||
}
|
Loading…
Reference in New Issue