From 53483ac8704181e963b880d51ab4a6e5c1aaf2ec Mon Sep 17 00:00:00 2001 From: mvvasilev Date: Sat, 18 May 2024 20:38:46 +0300 Subject: [PATCH] maybe rewrite with ecs, idk --- engine/ecs/component_type.go | 73 +++++++++++++++++++++ engine/ecs/ecs.go | 77 +++++++++------------- engine/ecs/ecs_test.go | 108 ++++++++++--------------------- engine/event.go | 22 +++++++ engine/render_context.go | 43 +++++------- game/component/drawables.go | 17 +++++ game/component/game_state.go | 16 +++++ game/component/input.go | 17 +++++ game/game_context.go | 38 ++--------- game/game_world.go | 52 +++++++++++++++ game/system/game_state_system.go | 27 ++++++++ game/system/input_system.go | 32 +++++++++ game/system/rendering_system.go | 65 +++++++++++++++++++ go.sum | 5 ++ main.go | 5 ++ 15 files changed, 416 insertions(+), 181 deletions(-) create mode 100644 engine/ecs/component_type.go create mode 100644 engine/event.go create mode 100644 game/component/drawables.go create mode 100644 game/component/game_state.go create mode 100644 game/component/input.go create mode 100644 game/game_world.go create mode 100644 game/system/game_state_system.go create mode 100644 game/system/input_system.go create mode 100644 game/system/rendering_system.go diff --git a/engine/ecs/component_type.go b/engine/ecs/component_type.go new file mode 100644 index 0000000..7ac9e3c --- /dev/null +++ b/engine/ecs/component_type.go @@ -0,0 +1,73 @@ +package ecs + +/* ================== ComponentType =================== */ + +type ComponentType uint64 + +const ( + ComponentType_0 ComponentType = 0b0000000000000000000000000000000000000000000000000000000000000000 + ComponentType_1 ComponentType = 0b0000000000000000000000000000000000000000000000000000000000000001 + ComponentType_2 ComponentType = 0b0000000000000000000000000000000000000000000000000000000000000010 + ComponentType_3 ComponentType = 0b0000000000000000000000000000000000000000000000000000000000000100 + ComponentType_4 ComponentType = 0b0000000000000000000000000000000000000000000000000000000000001000 + ComponentType_5 ComponentType = 0b0000000000000000000000000000000000000000000000000000000000010000 + ComponentType_6 ComponentType = 0b0000000000000000000000000000000000000000000000000000000000100000 + ComponentType_7 ComponentType = 0b0000000000000000000000000000000000000000000000000000000001000000 + ComponentType_8 ComponentType = 0b0000000000000000000000000000000000000000000000000000000010000000 + ComponentType_9 ComponentType = 0b0000000000000000000000000000000000000000000000000000000100000000 + ComponentType_10 ComponentType = 0b0000000000000000000000000000000000000000000000000000001000000000 + ComponentType_11 ComponentType = 0b0000000000000000000000000000000000000000000000000000010000000000 + ComponentType_12 ComponentType = 0b0000000000000000000000000000000000000000000000000000100000000000 + ComponentType_13 ComponentType = 0b0000000000000000000000000000000000000000000000000001000000000000 + ComponentType_14 ComponentType = 0b0000000000000000000000000000000000000000000000000010000000000000 + ComponentType_15 ComponentType = 0b0000000000000000000000000000000000000000000000000100000000000000 + ComponentType_16 ComponentType = 0b0000000000000000000000000000000000000000000000001000000000000000 + ComponentType_17 ComponentType = 0b0000000000000000000000000000000000000000000000010000000000000000 + ComponentType_18 ComponentType = 0b0000000000000000000000000000000000000000000000100000000000000000 + ComponentType_19 ComponentType = 0b0000000000000000000000000000000000000000000001000000000000000000 + ComponentType_20 ComponentType = 0b0000000000000000000000000000000000000000000010000000000000000000 + ComponentType_21 ComponentType = 0b0000000000000000000000000000000000000000000100000000000000000000 + ComponentType_22 ComponentType = 0b0000000000000000000000000000000000000000001000000000000000000000 + ComponentType_23 ComponentType = 0b0000000000000000000000000000000000000000010000000000000000000000 + ComponentType_24 ComponentType = 0b0000000000000000000000000000000000000000100000000000000000000000 + ComponentType_25 ComponentType = 0b0000000000000000000000000000000000000001000000000000000000000000 + ComponentType_26 ComponentType = 0b0000000000000000000000000000000000000010000000000000000000000000 + ComponentType_27 ComponentType = 0b0000000000000000000000000000000000000100000000000000000000000000 + ComponentType_28 ComponentType = 0b0000000000000000000000000000000000001000000000000000000000000000 + ComponentType_29 ComponentType = 0b0000000000000000000000000000000000010000000000000000000000000000 + ComponentType_30 ComponentType = 0b0000000000000000000000000000000000100000000000000000000000000000 + ComponentType_31 ComponentType = 0b0000000000000000000000000000000001000000000000000000000000000000 + ComponentType_32 ComponentType = 0b0000000000000000000000000000000010000000000000000000000000000000 + ComponentType_33 ComponentType = 0b0000000000000000000000000000000100000000000000000000000000000000 + ComponentType_34 ComponentType = 0b0000000000000000000000000000001000000000000000000000000000000000 + ComponentType_35 ComponentType = 0b0000000000000000000000000000010000000000000000000000000000000000 + ComponentType_36 ComponentType = 0b0000000000000000000000000000100000000000000000000000000000000000 + ComponentType_37 ComponentType = 0b0000000000000000000000000001000000000000000000000000000000000000 + ComponentType_38 ComponentType = 0b0000000000000000000000000010000000000000000000000000000000000000 + ComponentType_39 ComponentType = 0b0000000000000000000000000100000000000000000000000000000000000000 + ComponentType_40 ComponentType = 0b0000000000000000000000001000000000000000000000000000000000000000 + ComponentType_41 ComponentType = 0b0000000000000000000000010000000000000000000000000000000000000000 + ComponentType_42 ComponentType = 0b0000000000000000000000100000000000000000000000000000000000000000 + ComponentType_43 ComponentType = 0b0000000000000000000001000000000000000000000000000000000000000000 + ComponentType_44 ComponentType = 0b0000000000000000000010000000000000000000000000000000000000000000 + ComponentType_45 ComponentType = 0b0000000000000000000100000000000000000000000000000000000000000000 + ComponentType_46 ComponentType = 0b0000000000000000001000000000000000000000000000000000000000000000 + ComponentType_47 ComponentType = 0b0000000000000000010000000000000000000000000000000000000000000000 + ComponentType_48 ComponentType = 0b0000000000000000100000000000000000000000000000000000000000000000 + ComponentType_49 ComponentType = 0b0000000000000001000000000000000000000000000000000000000000000000 + ComponentType_50 ComponentType = 0b0000000000000010000000000000000000000000000000000000000000000000 + ComponentType_51 ComponentType = 0b0000000000000100000000000000000000000000000000000000000000000000 + ComponentType_52 ComponentType = 0b0000000000001000000000000000000000000000000000000000000000000000 + ComponentType_53 ComponentType = 0b0000000000010000000000000000000000000000000000000000000000000000 + ComponentType_54 ComponentType = 0b0000000000100000000000000000000000000000000000000000000000000000 + ComponentType_55 ComponentType = 0b0000000001000000000000000000000000000000000000000000000000000000 + ComponentType_56 ComponentType = 0b0000000010000000000000000000000000000000000000000000000000000000 + ComponentType_57 ComponentType = 0b0000000100000000000000000000000000000000000000000000000000000000 + ComponentType_58 ComponentType = 0b0000001000000000000000000000000000000000000000000000000000000000 + ComponentType_59 ComponentType = 0b0000010000000000000000000000000000000000000000000000000000000000 + ComponentType_60 ComponentType = 0b0000100000000000000000000000000000000000000000000000000000000000 + ComponentType_61 ComponentType = 0b0001000000000000000000000000000000000000000000000000000000000000 + ComponentType_62 ComponentType = 0b0010000000000000000000000000000000000000000000000000000000000000 + ComponentType_63 ComponentType = 0b0100000000000000000000000000000000000000000000000000000000000000 + ComponentType_64 ComponentType = 0b1000000000000000000000000000000000000000000000000000000000000000 +) diff --git a/engine/ecs/ecs.go b/engine/ecs/ecs.go index 7e93d80..901b703 100644 --- a/engine/ecs/ecs.go +++ b/engine/ecs/ecs.go @@ -1,10 +1,9 @@ package ecs import ( + "fmt" "slices" "time" - - "github.com/gdamore/tcell/v2" ) /* ===================== ECSError ===================== */ @@ -25,30 +24,8 @@ func (e *ECSError) Error() string { /* ==================================================== */ -/* ================== ComponentType =================== */ - -type ComponentType uint64 - -func TypeFrom(powerOf2 uint64) (ComponentType, *ECSError) { - if powerOf2 > 63 { - return 0, ecsError("Failure: Provided number is too high ( component types must be represented by a power of 2, up to 63 )") - } - - t := uint64(0) - - for i := range powerOf2 { - t *= i - } - - return ComponentType(t), nil -} - -/* ==================================================== */ - /* ====================== Types ======================= */ -type SystemOrder uint8 - type EntityId uint64 type ComponentMask uint64 @@ -90,7 +67,6 @@ func (c ComponentMask) ContainsMultiple(ids ComponentMask) bool { type System interface { Name() string Order() int - Input(world *World, e tcell.EventKey) Tick(world *World, deltaTime int64) } @@ -116,17 +92,11 @@ func CreateRandomEntityId() EntityId { return EntityId(time.Now().UnixNano()) } -func createEntity(components ...Component) *BasicEntity { - ent := &BasicEntity{ +func createEntity() *BasicEntity { + return &BasicEntity{ id: CreateRandomEntityId(), components: make(map[ComponentType]Component, 0), } - - for _, c := range components { - ent.components[c.Type()] = c - } - - return ent } func (ent *BasicEntity) Id() EntityId { @@ -192,14 +162,17 @@ type World struct { entities map[EntityId]*BasicEntity components map[ComponentType][]Component + singletons map[ComponentType]Component systems []System } func CreateWorld() *World { return &World{ - entities: make(map[EntityId]*BasicEntity, 0), - systems: make([]System, 0), - components: make(map[ComponentType][]Component, 0), + entities: make(map[EntityId]*BasicEntity, 0), + systems: make([]System, 0), + components: make(map[ComponentType][]Component, 0), + registeredComponentNames: make(map[ComponentType]string, 0), + singletons: make(map[ComponentType]Component, 0), } } @@ -225,6 +198,22 @@ func (w *World) QueryComponents(componentIds ...ComponentType) (components map[C return comps, nil } +func (w *World) FetchSingletonComponent(componentId ComponentType) (component Component, err *ECSError) { + comp := w.singletons[componentId] + + if comp == nil { + componentName := w.registeredComponentNames[componentId] + + return nil, ecsError(fmt.Sprintf("Failure: No singleton component of type %v could be found", componentName)) + } + + return comp, nil +} + +func (w *World) AddSingletonComponent(component Component) { + w.singletons[component.Type()] = component +} + func (w *World) RegisterComponentType(t ComponentType, name string) (err *ECSError) { if w.registeredComponentTypes.Contains(t) { return ecsError("Failure: ComponentType conflict, another component already exists with that type number") @@ -268,19 +257,21 @@ func (w *World) AddComponentToEntity(ent *BasicEntity, comp Component) (modified return ent, nil } -func (w *World) AddSystem(s System) (err *ECSError) { +func (w *World) AddSystem(s System) { w.systems = append(w.systems, s) slices.SortFunc(w.systems, func(a System, b System) int { return a.Order() - b.Order() }) - - return nil } func (w *World) CreateEntity(comps ...Component) *BasicEntity { - ent := createEntity(comps...) + ent := createEntity() w.entities[ent.Id()] = ent + for _, c := range comps { + w.AddComponentToEntity(ent, c) + } + return ent } @@ -304,10 +295,4 @@ func (w *World) Tick(dt int64) { } } -func (w *World) Input(e tcell.EventKey) { - for _, s := range w.systems { - s.Input(w, e) - } -} - /* ==================================================== */ diff --git a/engine/ecs/ecs_test.go b/engine/ecs/ecs_test.go index 12b5126..3d61ff4 100644 --- a/engine/ecs/ecs_test.go +++ b/engine/ecs/ecs_test.go @@ -3,8 +3,6 @@ package ecs import ( "reflect" "testing" - - "github.com/gdamore/tcell/v2" ) func Test_ecsError(t *testing.T) { @@ -171,9 +169,8 @@ func TestCreateRandomEntityId(t *testing.T) { } func Test_createEntity(t *testing.T) { - type args struct { - components []Component - } + type args struct{} + tests := []struct { name string args args @@ -183,7 +180,7 @@ func Test_createEntity(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := createEntity(tt.args.components...); !reflect.DeepEqual(got, tt.want) { + if got := createEntity(); !reflect.DeepEqual(got, tt.want) { t.Errorf("createEntity() = %v, want %v", got, tt.want) } }) @@ -535,41 +532,6 @@ func TestWorld_AddComponentToEntity(t *testing.T) { } } -func TestWorld_AddSystem(t *testing.T) { - type fields struct { - registeredComponentTypes ComponentMask - registeredComponentNames map[ComponentType]string - entities map[EntityId]*BasicEntity - components map[ComponentType][]Component - systems []System - } - type args struct { - s System - } - tests := []struct { - name string - fields fields - args args - wantErr *ECSError - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - w := &World{ - registeredComponentTypes: tt.fields.registeredComponentTypes, - registeredComponentNames: tt.fields.registeredComponentNames, - entities: tt.fields.entities, - components: tt.fields.components, - systems: tt.fields.systems, - } - if gotErr := w.AddSystem(tt.args.s); !reflect.DeepEqual(gotErr, tt.wantErr) { - t.Errorf("World.AddSystem() = %v, want %v", gotErr, tt.wantErr) - } - }) - } -} - func TestWorld_CreateEntity(t *testing.T) { type fields struct { registeredComponentTypes ComponentMask @@ -709,38 +671,6 @@ func TestWorld_Tick(t *testing.T) { } } -func TestWorld_Input(t *testing.T) { - type fields struct { - registeredComponentTypes ComponentMask - registeredComponentNames map[ComponentType]string - entities map[EntityId]*BasicEntity - components map[ComponentType][]Component - systems []System - } - type args struct { - e tcell.EventKey - } - tests := []struct { - name string - fields fields - args args - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - w := &World{ - registeredComponentTypes: tt.fields.registeredComponentTypes, - registeredComponentNames: tt.fields.registeredComponentNames, - entities: tt.fields.entities, - components: tt.fields.components, - systems: tt.fields.systems, - } - w.Input(tt.args.e) - }) - } -} - func TestWorld_FindEntitiesWithComponents(t *testing.T) { type fields struct { registeredComponentTypes ComponentMask @@ -775,3 +705,35 @@ func TestWorld_FindEntitiesWithComponents(t *testing.T) { }) } } + +func TestWorld_AddSystem(t *testing.T) { + type fields struct { + registeredComponentTypes ComponentMask + registeredComponentNames map[ComponentType]string + entities map[EntityId]*BasicEntity + components map[ComponentType][]Component + systems []System + } + type args struct { + s System + } + tests := []struct { + name string + fields fields + args args + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + w := &World{ + registeredComponentTypes: tt.fields.registeredComponentTypes, + registeredComponentNames: tt.fields.registeredComponentNames, + entities: tt.fields.entities, + components: tt.fields.components, + systems: tt.fields.systems, + } + w.AddSystem(tt.args.s) + }) + } +} diff --git a/engine/event.go b/engine/event.go new file mode 100644 index 0000000..e3e7f40 --- /dev/null +++ b/engine/event.go @@ -0,0 +1,22 @@ +package engine + +import "github.com/gdamore/tcell/v2" + +type Event interface { +} + +type InputEvent struct { + tcellEvent *tcell.EventKey +} + +func (e *InputEvent) TcellEvent() *tcell.EventKey { + return e.tcellEvent +} + +type ResizeEvent struct { + tcellEvent *tcell.EventResize +} + +func (e *ResizeEvent) TcellEvent() *tcell.EventResize { + return e.tcellEvent +} diff --git a/engine/render_context.go b/engine/render_context.go index 5879ab4..299bf11 100644 --- a/engine/render_context.go +++ b/engine/render_context.go @@ -2,7 +2,6 @@ package engine import ( "errors" - "fmt" "log" "github.com/gdamore/tcell/v2" @@ -40,16 +39,15 @@ func Multidraw(drawables ...Drawable) []Drawable { return arr } -type RenderContext struct { +type EngineContext struct { screen tcell.Screen view *views.ViewPort - events chan tcell.Event - quit chan struct{} - drawables chan Drawable + events chan tcell.Event + quit chan struct{} } -func CreateRenderContext() (*RenderContext, error) { +func InitEngine() (*EngineContext, error) { screen, sErr := tcell.NewScreen() if sErr != nil { @@ -87,22 +85,21 @@ func CreateRenderContext() (*RenderContext, error) { go screen.ChannelEvents(events, quit) - context := new(RenderContext) + context := new(EngineContext) context.screen = screen context.events = events context.quit = quit context.view = view - context.drawables = make(chan Drawable) return context, nil } -func (c *RenderContext) Stop() { +func (c *EngineContext) Stop() { c.screen.Fini() } -func (c *RenderContext) CollectInputEvents() []*tcell.EventKey { +func (c *EngineContext) CollectInputEvents() []*tcell.EventKey { events := make([]tcell.Event, len(c.events)) select { @@ -118,20 +115,14 @@ func (c *RenderContext) CollectInputEvents() []*tcell.EventKey { case *tcell.EventKey: inputEvents = append(inputEvents, ev) case *tcell.EventResize: - c.onResize(ev) + c.Resize(ev.Size()) } } return inputEvents } -func (c *RenderContext) DrawableQueue() chan Drawable { - return c.drawables -} - -func (c *RenderContext) onResize(ev *tcell.EventResize) { - width, height := ev.Size() - +func (c *EngineContext) Resize(width, height int) { c.screen.Clear() c.view.Resize( @@ -144,20 +135,16 @@ func (c *RenderContext) onResize(ev *tcell.EventResize) { c.screen.Sync() } -func (c *RenderContext) Draw(deltaTime int64, drawables []Drawable) { - fps := 1_000_000 / deltaTime - +func (c *EngineContext) Clear() { c.view.Clear() +} - msPerFrame := float32(fps) / 1000.0 - - fpsText := CreateText(0, 0, 16, 1, fmt.Sprintf("%vms", msPerFrame), tcell.StyleDefault) +func (c *EngineContext) Show() { + c.screen.Show() +} +func (c *EngineContext) Draw(drawables []Drawable) { for _, d := range drawables { d.Draw(c.view) } - - fpsText.Draw(c.view) - - c.screen.Show() } diff --git a/game/component/drawables.go b/game/component/drawables.go new file mode 100644 index 0000000..9d6e352 --- /dev/null +++ b/game/component/drawables.go @@ -0,0 +1,17 @@ +package component + +import ( + "mvvasilev/last_light/engine" + "mvvasilev/last_light/engine/ecs" +) + +const ComponentType_RenderableComponent = ecs.ComponentType_0 + +type DrawablesComponent struct { + Priority int + Drawables []engine.Drawable +} + +func (rc *DrawablesComponent) Type() ecs.ComponentType { + return ComponentType_RenderableComponent +} diff --git a/game/component/game_state.go b/game/component/game_state.go new file mode 100644 index 0000000..dbd4c0a --- /dev/null +++ b/game/component/game_state.go @@ -0,0 +1,16 @@ +package component + +import ( + "mvvasilev/last_light/engine/ecs" + "mvvasilev/last_light/game/state" +) + +const ComponentType_GameStateComponent = ecs.ComponentType_2 + +type GameStateComponent struct { + GameState *state.GameState +} + +func (gsc *GameStateComponent) Type() ecs.ComponentType { + return ComponentType_GameStateComponent +} diff --git a/game/component/input.go b/game/component/input.go new file mode 100644 index 0000000..5ef1fe6 --- /dev/null +++ b/game/component/input.go @@ -0,0 +1,17 @@ +package component + +import ( + "mvvasilev/last_light/engine/ecs" + + "github.com/gdamore/tcell/v2" +) + +const ComponentType_InputComponent = ecs.ComponentType_1 + +type InputComponent struct { + KeyEvents []*tcell.EventKey +} + +func (ic *InputComponent) Type() ecs.ComponentType { + return ComponentType_InputComponent +} diff --git a/game/game_context.go b/game/game_context.go index 53dbb45..e0ed50c 100644 --- a/game/game_context.go +++ b/game/game_context.go @@ -1,60 +1,30 @@ package game import ( - "log" - "mvvasilev/last_light/engine" - "os" "time" ) const TICK_RATE int64 = 50 // tick every 50ms ( 20 ticks per second ) type GameContext struct { - renderContext *engine.RenderContext - - game *Game + world *GameWorld } func CreateGameContext() *GameContext { gc := new(GameContext) - rc, err := engine.CreateRenderContext() - - if err != nil { - log.Fatalf("%~v", err) - } - - gc.renderContext = rc - gc.game = CreateGame() + gc.world = CreateGameWorld() return gc } func (gc *GameContext) Run() { lastLoop := time.Now() - lastTick := time.Now() for { - deltaTime := 1 + time.Since(lastLoop).Microseconds() + deltaTime := 1 + time.Since(lastLoop).Milliseconds() lastLoop = time.Now() - for _, e := range gc.renderContext.CollectInputEvents() { - gc.game.Input(e) - } - - if time.Since(lastTick).Milliseconds() >= TICK_RATE { - stop := !gc.game.Tick(deltaTime) - - if stop { - gc.renderContext.Stop() - os.Exit(0) - break - } - - lastTick = time.Now() - } - - drawables := gc.game.CollectDrawables() - gc.renderContext.Draw(deltaTime, drawables) + gc.world.Tick(deltaTime) } } diff --git a/game/game_world.go b/game/game_world.go new file mode 100644 index 0000000..1c78917 --- /dev/null +++ b/game/game_world.go @@ -0,0 +1,52 @@ +package game + +import ( + "log" + "mvvasilev/last_light/engine" + "mvvasilev/last_light/engine/ecs" + "mvvasilev/last_light/game/component" + "mvvasilev/last_light/game/system" +) + +type GameWorld struct { + ecs *ecs.World +} + +func CreateGameWorld() *GameWorld { + world := &GameWorld{ + ecs: ecs.CreateWorld(), + } + + engineContext, err := engine.InitEngine() + + if err != nil { + // TODO: error logs + log.Fatalf("%~v", err) + return nil + } + + world.ecs.RegisterComponentType(component.ComponentType_RenderableComponent, "RenderableComponent") + world.ecs.RegisterComponentType(component.ComponentType_InputComponent, "InputComponent") + world.ecs.RegisterComponentType(component.ComponentType_GameStateComponent, "GameStateComponent") + + world.ecs.AddSystem(system.CreateRenderingSystem(engineContext)) + world.ecs.AddSystem(system.CreateInputSystem(engineContext)) + world.ecs.AddSystem(system.CreateGameStateSystem()) + + world.ecs.AddSingletonComponent(&component.InputComponent{}) + + world.ecs.CreateEntity(&component.DrawablesComponent{ + Priority: 0, + Drawables: []engine.Drawable{}, + }) + + return world +} + +func (gw *GameWorld) World() *ecs.World { + return gw.ecs +} + +func (gw *GameWorld) Tick(dt int64) { + gw.ecs.Tick(dt) +} diff --git a/game/system/game_state_system.go b/game/system/game_state_system.go new file mode 100644 index 0000000..0107565 --- /dev/null +++ b/game/system/game_state_system.go @@ -0,0 +1,27 @@ +package system + +import ( + "math" + "mvvasilev/last_light/engine/ecs" + "mvvasilev/last_light/game/component" +) + +type GameStateSystem struct { +} + +func CreateGameStateSystem() *GameStateSystem { + return &GameStateSystem{} +} + +func (gss *GameStateSystem) Name() string { + return "GameStateSystem" +} + +func (gss *GameStateSystem) Order() int { + return math.MinInt + 100 +} + +func (gss *GameStateSystem) Tick(world *ecs.World, deltaTime int64) { + comp, err := world.FetchSingletonComponent(component.ComponentType_GameStateComponent) + +} diff --git a/game/system/input_system.go b/game/system/input_system.go new file mode 100644 index 0000000..1b3c5fd --- /dev/null +++ b/game/system/input_system.go @@ -0,0 +1,32 @@ +package system + +import ( + "math" + "mvvasilev/last_light/engine" + "mvvasilev/last_light/engine/ecs" + "mvvasilev/last_light/game/component" +) + +type InputSystem struct { + engineContext *engine.EngineContext +} + +func CreateInputSystem(ec *engine.EngineContext) *InputSystem { + return &InputSystem{ + engineContext: ec, + } +} + +func (is *InputSystem) Name() string { + return "InputSystem" +} + +func (is *InputSystem) Order() int { + return math.MinInt +} + +func (is *InputSystem) Tick(world *ecs.World, deltaTime int64) { + world.AddSingletonComponent(&component.InputComponent{ + KeyEvents: is.engineContext.CollectInputEvents(), + }) +} diff --git a/game/system/rendering_system.go b/game/system/rendering_system.go new file mode 100644 index 0000000..9589cfb --- /dev/null +++ b/game/system/rendering_system.go @@ -0,0 +1,65 @@ +package system + +import ( + "fmt" + "math" + "mvvasilev/last_light/engine" + "mvvasilev/last_light/engine/ecs" + "mvvasilev/last_light/game/component" + "slices" + + "github.com/gdamore/tcell/v2" +) + +type RenderingSystem struct { + engineContext *engine.EngineContext +} + +func CreateRenderingSystem(renderContext *engine.EngineContext) *RenderingSystem { + return &RenderingSystem{ + engineContext: renderContext, + } +} + +func (rs *RenderingSystem) Name() string { + return "RenderingSystem" +} + +func (rs *RenderingSystem) Order() int { + return math.MaxInt +} + +func (rs *RenderingSystem) Tick(world *ecs.World, deltaTime int64) { + comps, err := world.QueryComponents(component.ComponentType_RenderableComponent) + + if err != nil { + // Skip this frame since an error occured // TODO: error logging + return + } + + components := comps[component.ComponentType_RenderableComponent] + + slices.SortFunc(components, func(a ecs.Component, b ecs.Component) int { + aDrawable := a.(*component.DrawablesComponent) + bDrawable := b.(*component.DrawablesComponent) + + return aDrawable.Priority - bDrawable.Priority + }) + + fps := 1_000 / deltaTime + + msPerFrame := float32(fps) / 1000.0 + + fpsText := engine.CreateText(0, 0, 16, 1, fmt.Sprintf("%vms", msPerFrame), tcell.StyleDefault) + + rs.engineContext.Clear() + + for _, c := range components { + drawables := c.(*component.DrawablesComponent).Drawables + rs.engineContext.Draw(drawables) + } + + rs.engineContext.Draw([]engine.Drawable{fpsText}) + + rs.engineContext.Show() +} diff --git a/go.sum b/go.sum index f198c9c..0ab420a 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,15 @@ github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= +github.com/gdamore/tcell v1.4.0 h1:vUnHwJRvcPQa3tzi+0QI4U9JINXYJlOz9yiaiPQ2wMU= +github.com/gdamore/tcell v1.4.0/go.mod h1:vxEiSDZdW3L+Uhjii9c3375IlDmR05bzxY404ZVSMo0= github.com/gdamore/tcell/v2 v2.7.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAYU= github.com/gdamore/tcell/v2 v2.7.4/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -24,6 +28,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/main.go b/main.go index 8ccb7fc..fffb02b 100644 --- a/main.go +++ b/main.go @@ -6,3 +6,8 @@ func main() { gc := game.CreateGameContext() gc.Run() } + +func runGame() { + gc := game.CreateGameContext() + gc.Run() +}