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