Skip to content

Commit cdba5ce

Browse files
committed
Working simple general purpose FSM
1 parent 07e1aa0 commit cdba5ce

8 files changed

Lines changed: 248 additions & 36 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ set(ENGINESOURCE
187187
src/Engine/Files/filemanager.cpp
188188
src/Engine/Files/fileparser.cpp
189189
src/Engine/Profiler/profiler.cpp
190+
src/Engine/Helpers/fsm.cpp
190191
src/Engine/Helpers/openglobject.cpp
191192
src/Engine/Helpers/tinyfiledialogs.cpp
192193
src/Engine/Input/input.cpp

exemples/StandardSys/application.cpp

Lines changed: 69 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
#include "Renderer/renderer.h"
1414

15+
#include "Helpers/fsm.h"
16+
1517
using namespace pg;
1618

1719
#ifdef PG_AUTO_CONVERT_EVENTS_TO_STANDARD
@@ -28,11 +30,11 @@ int test = 0;
2830
StandardSystemImpl* createEventNotificationSystem()
2931
{
3032
return createStandardSystem("EventNotification")
31-
.onInit([](StandardSystemHandle* sys) {
33+
.onInit([](StandardSystemHandle*) {
3234
// Initialize system
3335
LOG_INFO("EventNotification", "System initialized");
3436
})
35-
.onEvent("BasicEvent", [](StandardSystemHandle* sys, const StandardEvent& event) {
37+
.onEvent("BasicEvent", [](StandardSystemHandle*, const StandardEvent& event) {
3638
bool verbose = true;
3739

3840
test++;
@@ -70,7 +72,7 @@ StandardSystemImpl* createEventNotificationSystem()
7072
StandardSystemImpl* createScriptEventNotificationSystem()
7173
{
7274
return createStandardSystem("ScriptEventNotification")
73-
.onInit([](StandardSystemHandle* sys) {
75+
.onInit([](StandardSystemHandle*) {
7476
// Initialize system
7577
LOG_INFO("ScriptEventNotification", "System initialized");
7678
})
@@ -82,7 +84,7 @@ StandardSystemImpl* createScriptEventNotificationSystem()
8284
StandardSystemImpl* createSimplePositionSystem()
8385
{
8486
return createStandardSystem("SimplePosition")
85-
.onInit([](StandardSystemHandle* sys) {
87+
.onInit([](StandardSystemHandle*) {
8688
// Initialize system
8789
LOG_INFO("SimplePosition", "System initialized");
8890
})
@@ -96,7 +98,7 @@ StandardSystemImpl* createSimplePositionSystem()
9698
StandardSystemImpl* createSimpleExecSystem()
9799
{
98100
return createStandardSystem("SimpleExec")
99-
.onInit([](StandardSystemHandle* sys) {
101+
.onInit([](StandardSystemHandle*) {
100102
// Initialize system
101103
LOG_INFO("SimpleExec", "System initialized");
102104
})
@@ -107,7 +109,7 @@ StandardSystemImpl* createSimpleExecSystem()
107109
StandardSystemImpl* createCompExecSystem()
108110
{
109111
return createStandardSystem("CompExec")
110-
.onInit([](StandardSystemHandle* sys) {
112+
.onInit([](StandardSystemHandle*) {
111113
// Initialize system
112114
LOG_INFO("CompExec", "System initialized");
113115
})
@@ -119,7 +121,7 @@ StandardSystemImpl* createCompExecSystem()
119121
StandardSystemImpl* createCompExecReactorSystem()
120122
{
121123
return createStandardSystem("CompExecReactor")
122-
.onInit([](StandardSystemHandle* sys) {
124+
.onInit([](StandardSystemHandle*) {
123125
// Initialize system
124126
LOG_INFO("CompExec", "System initialized");
125127
})
@@ -130,7 +132,7 @@ StandardSystemImpl* createCompExecReactorSystem()
130132
StandardSystemImpl* createMouseClickHandlerSystem()
131133
{
132134
return createStandardSystem("MouseClickHandler")
133-
.onInit([](StandardSystemHandle* sys) {
135+
.onInit([](StandardSystemHandle*) {
134136
// Initialize system
135137
LOG_INFO("CompExec", "System initialized");
136138
})
@@ -141,7 +143,7 @@ StandardSystemImpl* createMouseClickHandlerSystem()
141143
StandardSystemImpl* createKeyHandlerSystem()
142144
{
143145
return createStandardSystem("MouseClickHandler")
144-
.onInit([](StandardSystemHandle* sys) {
146+
.onInit([](StandardSystemHandle*) {
145147
// Initialize system
146148
LOG_INFO("CompExec", "System initialized");
147149
})
@@ -213,6 +215,9 @@ GameApp::GameApp(const std::string &appName) : engine(appName)
213215
{
214216
engine.setSetupFunction([this](EntitySystem& ecs, Window& window)
215217
{
218+
// Todo O3 optimization breaks the event system for some reason
219+
ecs.setVMOptimizationLevel(VmOptimizationLevel::O0);
220+
216221
ecs.registerSystem(createEventNotificationSystem());
217222

218223
// Basic Event without a custom value
@@ -343,6 +348,61 @@ GameApp::GameApp(const std::string &appName) : engine(appName)
343348

344349
ecs.registerSystem(createFPSSystem());
345350

351+
ecs.createSystem<FSMSystem>();
352+
353+
auto ent = ecs.createEntity();
354+
355+
auto fsm = ent.attach<FiniteStateMachine>();
356+
357+
FSMState idleState("idle");
358+
359+
idleState.setEnterCallback([](FSMState&) {
360+
LOG_INFO("FSM", "Entering idle state");
361+
});
362+
363+
idleState.setExitCallback([](FSMState&) {
364+
LOG_INFO("FSM", "Exiting idle state");
365+
});
366+
367+
idleState.setEventCallback("OnSDLScanCode", [](const StandardEvent& event, FSMState& state) {
368+
auto key = event.getElement("key").toString();
369+
370+
LOG_INFO("FSM", "Idle State received scan code: " << key);
371+
372+
if (key == "A")
373+
{
374+
state.getFSM()->setState("base", "base");
375+
}
376+
});
377+
378+
FSMState baseState("base");
379+
380+
baseState.setEnterCallback([](FSMState&) {
381+
LOG_INFO("FSM", "Entering base state");
382+
});
383+
384+
baseState.setExitCallback([](FSMState&) {
385+
LOG_INFO("FSM", "Exiting base state");
386+
});
387+
388+
baseState.setEventCallback("OnSDLScanCode", [](const StandardEvent& event, FSMState& state) {
389+
auto key = event.getElement("key").toString();
390+
391+
LOG_INFO("FSM", "Idle State received scan code: " << key);
392+
393+
if (key == "A" or key == "D")
394+
{
395+
state.getFSM()->setState("base", "idle");
396+
}
397+
});
398+
399+
fsm->registerState(idleState);
400+
fsm->registerState(baseState);
401+
402+
fsm->setState("base", "idle");
403+
404+
// fsm->setState("base", "base");
405+
346406
// window.receivedQuitRequest();
347407

348408
});

readme.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
[![CI](https://github.com/Gallasko/PgEngine/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/Gallasko/PgEngine/actions/workflows/main.yml) [![Documentation Status](https://readthedocs.org/projects/columbaengine/badge/?version=latest)](https://columbaengine.readthedocs.io/en/latest/?badge=latest) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
44

5+
**Website:** [columbaengine.org](https://columbaengine.org/) | **Discord:** [Join our community](https://discord.gg/un4VtehX3W)
6+
57
## About
68

79
ColumbaEngine is a **completely free and open source** game engine built with modern C++ and a pure Entity Component System (ECS) architecture. Whether you're building 2D games, prototyping ideas, or learning game engine development, ColumbaEngine provides a solid foundation without any licensing fees or royalties.
@@ -126,6 +128,7 @@ ColumbaEngine is perfect for:
126128
## Examples & Games
127129

128130
- **Full Tetris Clone** - Complete implementation showcasing engine capabilities
131+
- **Game Examples & Blog Posts** - Visit [columbaengine.org](https://columbaengine.org/) for tutorials, blog posts, and game examples
129132
- **More Examples** - Available at [pigeoncodeur.itch.io](https://pigeoncodeur.itch.io/)
130133

131134
## Development
@@ -180,6 +183,8 @@ $env:TF_ENABLE_PROFILER="profile.json"
180183

181184
We're actively seeking contributors to help build a thriving community around ColumbaEngine!
182185

186+
Join our Discord community: **[https://discord.gg/un4VtehX3W](https://discord.gg/un4VtehX3W)**
187+
183188
### Current Priorities
184189
1. **Documentation** - Help us improve tutorials and API documentation
185190
2. **Platform Support** - Test and improve cross-platform compatibility
@@ -233,7 +238,7 @@ This project is under active development. While the 2D pipeline is production-re
233238
For more help, please:
234239
- Check the [documentation](https://columbaengine.readthedocs.io)
235240
- Open an [issue](https://github.com/Gallasko/ColumbaEngine/issues)
236-
- Join our community discussions
241+
- Join our [Discord community](https://discord.gg/un4VtehX3W)
237242

238243
## License
239244

src/Engine/ECS/entitysystem.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,9 @@ namespace pg
669669

670670
setOptimizationPasses(vm);
671671

672+
// Todo add a flag to enable this
673+
// vm.enableOptimizationDebugging();
674+
672675
// Setup the VM with necessary bindings and references
673676
// For example, bind the ECS reference to the VM for script access
674677
// This is a placeholder implementation; actual implementation may vary

src/Engine/ECS/entitysystem.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,8 @@ namespace pg
582582
/** Return the registry of the ECS, mainly for testing purposes */
583583
inline constexpr const ComponentRegistry* getComponentRegistry() const noexcept { return &registry; }
584584

585+
inline ComponentRegistry* getComponentRegistry() noexcept { return &registry; }
586+
585587
inline size_t getNbEntities() const { LOG_THIS_MEMBER("ECS"); return entityPool.nbElements() - 1; }
586588

587589
inline Entity* getEntity(_unique_id id) const { LOG_THIS_MEMBER("ECS"); return entityPool.atEntity(id); }

src/Engine/Helpers/fsm.cpp

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#include "fsm.h"
2+
3+
#include "../ECS/entitysystem.h"
4+
5+
namespace pg
6+
{
7+
void FSMState::setEnterScript(const std::string&)
8+
{
9+
// enterScript = script;
10+
}
11+
12+
void FSMState::setExitScript(const std::string&)
13+
{
14+
// exitScript = script;
15+
}
16+
17+
void FSMState::setEventScript(const std::string&)
18+
{
19+
// eventScript = script;
20+
}
21+
22+
void FiniteStateMachine::onDeletion(EntityRef)
23+
{
24+
for (const auto& trigger : triggers)
25+
{
26+
ecsRef->getComponentRegistry()->removeStandardEventListener(trigger, this);
27+
}
28+
}
29+
30+
void FiniteStateMachine::registerState(const FSMState& state)
31+
{
32+
auto& newState = staticStates[state.name] = state;
33+
34+
newState._FSM = this;
35+
36+
for (const auto& [trigger, _] : state.eventCallbacks)
37+
{
38+
auto inserted = triggers.insert(trigger);
39+
40+
if (inserted.second)
41+
{
42+
ecsRef->sendEvent(AddListenerToEvent{entityId, trigger});
43+
}
44+
}
45+
}
46+
47+
void FiniteStateMachine::setState(const std::string& fsm, const FSMState& state)
48+
{
49+
LOG_INFO("FiniteStateMachine", "Setting state '" << state.name << "' for FSM '" << fsm << "' on entity " << entityId);
50+
51+
auto& oldState = currentStates[fsm];
52+
53+
oldState.onExit();
54+
55+
// for (const auto& trigger : oldState.triggers)
56+
// {
57+
// auto& vec = triggeredStates[trigger];
58+
// vec.erase(std::remove(vec.begin(), vec.end(), oldState), vec.end());
59+
60+
// if (vec.empty())
61+
// {
62+
// triggeredStates.erase(trigger);
63+
// ecsRef->sendEvent(RemoveListenerFromEvent{entityId, trigger});
64+
// }
65+
// }
66+
67+
auto& newState = currentStates[fsm] = state;
68+
69+
newState.currentFSM = fsm;
70+
71+
for (const auto& [trigger, _] : state.eventCallbacks)
72+
{
73+
auto inserted = triggers.insert(trigger);
74+
75+
if (inserted.second)
76+
{
77+
ecsRef->sendEvent(AddListenerToEvent{entityId, trigger});
78+
}
79+
}
80+
81+
newState.onEnter();
82+
}
83+
84+
void FSMSystem::onEvent(const AddListenerToEvent& event)
85+
{
86+
LOG_THIS_MEMBER("FSMSystem");
87+
88+
auto entity = world()->getEntity(event.id);
89+
90+
if (entity && entity->has<FiniteStateMachine>())
91+
{
92+
auto fsm = entity->get<FiniteStateMachine>();
93+
94+
world()->getComponentRegistry()->addStandardEventListener(event.eventName, fsm.component);
95+
}
96+
}
97+
}

0 commit comments

Comments
 (0)