Event system
This commit is contained in:
@@ -58,6 +58,10 @@ set(EDITSCENE_SOURCES
|
||||
systems/ActuatorSystem.cpp
|
||||
ui/ActuatorEditor.cpp
|
||||
components/ActuatorModule.cpp
|
||||
systems/EventBus.cpp
|
||||
systems/EventHandlerSystem.cpp
|
||||
ui/EventHandlerEditor.cpp
|
||||
components/EventHandlerModule.cpp
|
||||
systems/PrefabSystem.cpp
|
||||
ui/PrefabInstanceEditor.cpp
|
||||
|
||||
@@ -197,6 +201,10 @@ set(EDITSCENE_HEADERS
|
||||
systems/GoapPlannerSystem.hpp
|
||||
components/Actuator.hpp
|
||||
ui/ActuatorEditor.hpp
|
||||
systems/EventBus.hpp
|
||||
components/EventHandler.hpp
|
||||
systems/EventHandlerSystem.hpp
|
||||
ui/EventHandlerEditor.hpp
|
||||
components/PrefabInstance.hpp
|
||||
ui/PrefabInstanceEditor.hpp
|
||||
|
||||
|
||||
@@ -74,6 +74,8 @@
|
||||
#include "components/GoapRunner.hpp"
|
||||
#include "components/PathFollowing.hpp"
|
||||
#include "systems/ActuatorSystem.hpp"
|
||||
#include "systems/EventHandlerSystem.hpp"
|
||||
#include "components/EventHandler.hpp"
|
||||
|
||||
#include <OgreRTShaderSystem.h>
|
||||
#include <imgui.h>
|
||||
@@ -356,6 +358,10 @@ void EditorApp::setup()
|
||||
m_actuatorSystem = std::make_unique<ActuatorSystem>(
|
||||
m_world, m_sceneMgr, this, m_behaviorTreeSystem.get());
|
||||
|
||||
// Setup Event Handler system
|
||||
m_eventHandlerSystem = std::make_unique<EventHandlerSystem>(
|
||||
m_world, m_behaviorTreeSystem.get());
|
||||
|
||||
// Setup GOAP Runner system
|
||||
m_goapRunnerSystem = std::make_unique<GoapRunnerSystem>(
|
||||
m_world, m_sceneMgr, m_smartObjectSystem.get(),
|
||||
@@ -618,6 +624,9 @@ void EditorApp::setupECS()
|
||||
// Register Actuator component
|
||||
m_world.component<ActuatorComponent>();
|
||||
|
||||
// Register Event Handler component
|
||||
m_world.component<EventHandlerComponent>();
|
||||
|
||||
// Register GOAP Planner component
|
||||
m_world.component<GoapPlannerComponent>();
|
||||
|
||||
@@ -830,6 +839,11 @@ bool EditorApp::frameRenderingQueued(const Ogre::FrameEvent &evt)
|
||||
m_actuatorSystem->update(evt.timeSinceLastFrame);
|
||||
}
|
||||
|
||||
/* --- Event Handler system (event-driven BTs) --- */
|
||||
if (m_eventHandlerSystem) {
|
||||
m_eventHandlerSystem->update(evt.timeSinceLastFrame);
|
||||
}
|
||||
|
||||
/* --- Dynamic physics (characters after static world) --- */
|
||||
|
||||
if (m_characterSystem) {
|
||||
|
||||
@@ -40,6 +40,7 @@ class GoapRunnerSystem;
|
||||
class PathFollowingSystem;
|
||||
class GoapPlannerSystem;
|
||||
class ActuatorSystem;
|
||||
class EventHandlerSystem;
|
||||
class EditorApp;
|
||||
|
||||
/**
|
||||
@@ -190,6 +191,10 @@ public:
|
||||
{
|
||||
return m_actuatorSystem.get();
|
||||
}
|
||||
EventHandlerSystem *getEventHandlerSystem() const
|
||||
{
|
||||
return m_eventHandlerSystem.get();
|
||||
}
|
||||
Ogre::ImGuiOverlay *getImGuiOverlay() const
|
||||
{
|
||||
return m_imguiOverlay;
|
||||
@@ -234,6 +239,7 @@ private:
|
||||
std::unique_ptr<PathFollowingSystem> m_pathFollowingSystem;
|
||||
std::unique_ptr<GoapPlannerSystem> m_goapPlannerSystem;
|
||||
std::unique_ptr<ActuatorSystem> m_actuatorSystem;
|
||||
std::unique_ptr<EventHandlerSystem> m_eventHandlerSystem;
|
||||
|
||||
// Game systems
|
||||
|
||||
|
||||
@@ -73,7 +73,8 @@ struct BehaviorTreeNode {
|
||||
type == "checkBit" || type == "setValue" ||
|
||||
type == "checkValue" || type == "blackboardDump" ||
|
||||
type == "delay" || type == "teleportToChild" ||
|
||||
type == "disablePhysics" || type == "enablePhysics";
|
||||
type == "disablePhysics" || type == "enablePhysics" ||
|
||||
type == "sendEvent";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
21
src/features/editScene/components/EventHandler.hpp
Normal file
21
src/features/editScene/components/EventHandler.hpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#ifndef EDITSCENE_EVENT_HANDLER_HPP
|
||||
#define EDITSCENE_EVENT_HANDLER_HPP
|
||||
#pragma once
|
||||
|
||||
#include <Ogre.h>
|
||||
|
||||
/**
|
||||
* Event-driven behavior tree handler component.
|
||||
*
|
||||
* When the specified event is received, the referenced GoapAction's
|
||||
* behavior tree is executed for this entity. Event parameters are
|
||||
* merged into the entity's GoapBlackboard before the tree runs and
|
||||
* cleaned up when the tree completes.
|
||||
*/
|
||||
struct EventHandlerComponent {
|
||||
Ogre::String eventName;
|
||||
Ogre::String actionName;
|
||||
bool enabled = true;
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_EVENT_HANDLER_HPP
|
||||
21
src/features/editScene/components/EventHandlerModule.cpp
Normal file
21
src/features/editScene/components/EventHandlerModule.cpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#include "EventHandler.hpp"
|
||||
#include "../ui/ComponentRegistration.hpp"
|
||||
#include "../ui/EventHandlerEditor.hpp"
|
||||
|
||||
REGISTER_COMPONENT_GROUP("Event Handler", "Game", EventHandlerComponent,
|
||||
EventHandlerEditor)
|
||||
{
|
||||
registry.registerComponent<EventHandlerComponent>(
|
||||
"Event Handler", "Game",
|
||||
std::make_unique<EventHandlerEditor>(),
|
||||
// Adder
|
||||
[](flecs::entity e) {
|
||||
if (!e.has<EventHandlerComponent>())
|
||||
e.set<EventHandlerComponent>({});
|
||||
},
|
||||
// Remover
|
||||
[](flecs::entity e) {
|
||||
if (e.has<EventHandlerComponent>())
|
||||
e.remove<EventHandlerComponent>();
|
||||
});
|
||||
}
|
||||
@@ -188,9 +188,32 @@ Ogre::String GoapBlackboard::dump() const
|
||||
")\n";
|
||||
}
|
||||
|
||||
if (!stringValues.empty()) {
|
||||
result += " String values:\n";
|
||||
for (const auto &pair : stringValues)
|
||||
result += " " + pair.first + " = " + pair.second + "\n";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void GoapBlackboard::merge(const GoapBlackboard &other)
|
||||
{
|
||||
// Merge bits
|
||||
bits = (bits & ~other.mask) | (other.bits & other.mask);
|
||||
mask |= other.mask;
|
||||
|
||||
// Merge values
|
||||
for (const auto &pair : other.values)
|
||||
values[pair.first] = pair.second;
|
||||
for (const auto &pair : other.floatValues)
|
||||
floatValues[pair.first] = pair.second;
|
||||
for (const auto &pair : other.vec3Values)
|
||||
vec3Values[pair.first] = pair.second;
|
||||
for (const auto &pair : other.stringValues)
|
||||
stringValues[pair.first] = pair.second;
|
||||
}
|
||||
|
||||
std::vector<int> GoapBlackboard::getSetBits() const
|
||||
{
|
||||
std::vector<int> result;
|
||||
|
||||
@@ -35,6 +35,9 @@ struct GoapBlackboard {
|
||||
// Named Vector3 values — runtime character state
|
||||
std::unordered_map<std::string, Ogre::Vector3> vec3Values;
|
||||
|
||||
// Named string values — event params, tags, etc.
|
||||
std::unordered_map<std::string, Ogre::String> stringValues;
|
||||
|
||||
GoapBlackboard() = default;
|
||||
|
||||
/* --- Bit accessors --- */
|
||||
@@ -148,6 +151,34 @@ struct GoapBlackboard {
|
||||
vec3Values.erase(key);
|
||||
}
|
||||
|
||||
/* --- String value accessors --- */
|
||||
void setStringValue(const std::string &key, const Ogre::String &value)
|
||||
{
|
||||
stringValues[key] = value;
|
||||
}
|
||||
|
||||
Ogre::String getStringValue(const std::string &key,
|
||||
const Ogre::String &defaultValue = "") const
|
||||
{
|
||||
auto it = stringValues.find(key);
|
||||
if (it != stringValues.end())
|
||||
return it->second;
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
bool hasStringValue(const std::string &key) const
|
||||
{
|
||||
return stringValues.find(key) != stringValues.end();
|
||||
}
|
||||
|
||||
void removeStringValue(const std::string &key)
|
||||
{
|
||||
stringValues.erase(key);
|
||||
}
|
||||
|
||||
/* --- Merge another blackboard into this one --- */
|
||||
void merge(const GoapBlackboard &other);
|
||||
|
||||
/* --- Generic scalar lookup (tries int then float) --- */
|
||||
bool getScalarValue(const std::string &key, float &out) const;
|
||||
|
||||
@@ -171,6 +202,7 @@ struct GoapBlackboard {
|
||||
values.clear();
|
||||
floatValues.clear();
|
||||
vec3Values.clear();
|
||||
stringValues.clear();
|
||||
}
|
||||
|
||||
Ogre::String dump() const;
|
||||
@@ -191,7 +223,8 @@ struct GoapBlackboard {
|
||||
bitmask == other.bitmask &&
|
||||
values == other.values &&
|
||||
floatValues == other.floatValues &&
|
||||
vec3Values == other.vec3Values;
|
||||
vec3Values == other.vec3Values &&
|
||||
stringValues == other.stringValues;
|
||||
}
|
||||
|
||||
bool operator!=(const GoapBlackboard &other) const
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "AnimationTreeSystem.hpp"
|
||||
#include "CharacterSystem.hpp"
|
||||
#include "SmartObjectSystem.hpp"
|
||||
#include "EventBus.hpp"
|
||||
#include "../components/BehaviorTree.hpp"
|
||||
#include "../components/ActionDatabase.hpp"
|
||||
#include "../components/GoapBlackboard.hpp"
|
||||
@@ -16,6 +17,90 @@
|
||||
|
||||
static float g_epsilon = 0.0001f;
|
||||
|
||||
static bool parseValueString(const Ogre::String &str, int &outInt,
|
||||
float &outFloat, Ogre::Vector3 &outVec3, int &type);
|
||||
|
||||
/** Parse "key=val,key2=val2" params into a GoapBlackboard.
|
||||
* Values are auto-detected: int, float, vec3 (x,y,z), or quoted string. */
|
||||
static void parseEventParams(const Ogre::String &str, GoapBlackboard &out)
|
||||
{
|
||||
if (str.empty())
|
||||
return;
|
||||
|
||||
const char *s = str.c_str();
|
||||
while (*s) {
|
||||
// Skip whitespace
|
||||
while (*s == ' ' || *s == '\t')
|
||||
s++;
|
||||
if (!*s)
|
||||
break;
|
||||
|
||||
// Find key
|
||||
const char *keyStart = s;
|
||||
while (*s && *s != '=' && *s != ',')
|
||||
s++;
|
||||
Ogre::String key(keyStart, static_cast<size_t>(s - keyStart));
|
||||
// Trim trailing spaces from key
|
||||
while (!key.empty() &&
|
||||
(key.back() == ' ' || key.back() == '\t'))
|
||||
key.pop_back();
|
||||
|
||||
if (*s != '=') {
|
||||
while (*s && *s != ',')
|
||||
s++;
|
||||
if (*s == ',')
|
||||
s++;
|
||||
continue;
|
||||
}
|
||||
s++; // skip '='
|
||||
|
||||
// Skip whitespace before value
|
||||
while (*s == ' ' || *s == '\t')
|
||||
s++;
|
||||
|
||||
// Find value end (next comma or end)
|
||||
const char *valStart = s;
|
||||
bool inQuotes = false;
|
||||
while (*s && (*s != ',' || inQuotes)) {
|
||||
if (*s == '"')
|
||||
inQuotes = !inQuotes;
|
||||
s++;
|
||||
}
|
||||
Ogre::String val(valStart, static_cast<size_t>(s - valStart));
|
||||
// Trim trailing spaces from value
|
||||
while (!val.empty() &&
|
||||
(val.back() == ' ' || val.back() == '\t'))
|
||||
val.pop_back();
|
||||
|
||||
// Strip quotes if present
|
||||
if (val.size() >= 2 && val.front() == '"' &&
|
||||
val.back() == '"') {
|
||||
val = val.substr(1, val.size() - 2);
|
||||
out.setStringValue(key, val);
|
||||
} else {
|
||||
// Try int/float/vec3
|
||||
int iVal;
|
||||
float fVal;
|
||||
Ogre::Vector3 vVal;
|
||||
int vType;
|
||||
if (parseValueString(val, iVal, fVal, vVal, vType)) {
|
||||
if (vType == 0)
|
||||
out.setValue(key, iVal);
|
||||
else if (vType == 1)
|
||||
out.setFloatValue(key, fVal);
|
||||
else
|
||||
out.setVec3Value(key, vVal);
|
||||
} else {
|
||||
// Fallback to string
|
||||
out.setStringValue(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
if (*s == ',')
|
||||
s++;
|
||||
}
|
||||
}
|
||||
|
||||
static bool parseBitIndex(const Ogre::String &str, int &out)
|
||||
{
|
||||
// Try numeric first
|
||||
@@ -233,6 +318,15 @@ BehaviorTreeSystem::evaluateNode(const BehaviorTreeNode &node, flecs::entity e,
|
||||
return Status::success;
|
||||
}
|
||||
|
||||
if (node.type == "sendEvent") {
|
||||
if (isNewlyActive(state, &node)) {
|
||||
GoapBlackboard params;
|
||||
parseEventParams(node.params, params);
|
||||
EventBus::getInstance().send(node.name, params);
|
||||
}
|
||||
return Status::success;
|
||||
}
|
||||
|
||||
if (node.type == "delay") {
|
||||
char key[32];
|
||||
snprintf(key, sizeof(key), "%p", (void *)&node);
|
||||
|
||||
78
src/features/editScene/systems/EventBus.cpp
Normal file
78
src/features/editScene/systems/EventBus.cpp
Normal file
@@ -0,0 +1,78 @@
|
||||
#include "EventBus.hpp"
|
||||
|
||||
EventBus &EventBus::getInstance()
|
||||
{
|
||||
static EventBus instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
EventBus::ListenerId EventBus::subscribe(const Ogre::String &eventName,
|
||||
Callback cb)
|
||||
{
|
||||
ListenerId id = m_nextId++;
|
||||
m_listeners[eventName].push_back({ id, std::move(cb) });
|
||||
m_idToEvent[id] = eventName;
|
||||
return id;
|
||||
}
|
||||
|
||||
void EventBus::unsubscribe(ListenerId id)
|
||||
{
|
||||
auto it = m_idToEvent.find(id);
|
||||
if (it == m_idToEvent.end())
|
||||
return;
|
||||
|
||||
const Ogre::String &eventName = it->second;
|
||||
auto lit = m_listeners.find(eventName);
|
||||
if (lit != m_listeners.end()) {
|
||||
auto &vec = lit->second;
|
||||
for (auto vit = vec.begin(); vit != vec.end(); ++vit) {
|
||||
if (vit->id == id) {
|
||||
vec.erase(vit);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (vec.empty())
|
||||
m_listeners.erase(lit);
|
||||
}
|
||||
m_idToEvent.erase(it);
|
||||
}
|
||||
|
||||
void EventBus::send(const Ogre::String &eventName,
|
||||
const GoapBlackboard ¶ms)
|
||||
{
|
||||
auto lit = m_listeners.find(eventName);
|
||||
if (lit == m_listeners.end())
|
||||
return;
|
||||
|
||||
// Copy the listener list in case a callback mutates subscriptions
|
||||
auto listeners = lit->second;
|
||||
for (const auto &listener : listeners) {
|
||||
if (listener.callback)
|
||||
listener.callback(eventName, params);
|
||||
}
|
||||
}
|
||||
|
||||
void EventBus::send(const Ogre::String &eventName,
|
||||
const Ogre::String ¶mName, int value)
|
||||
{
|
||||
GoapBlackboard params;
|
||||
params.setValue(paramName, value);
|
||||
send(eventName, params);
|
||||
}
|
||||
|
||||
void EventBus::send(const Ogre::String &eventName,
|
||||
const Ogre::String ¶mName, float value)
|
||||
{
|
||||
GoapBlackboard params;
|
||||
params.setFloatValue(paramName, value);
|
||||
send(eventName, params);
|
||||
}
|
||||
|
||||
void EventBus::send(const Ogre::String &eventName,
|
||||
const Ogre::String ¶mName,
|
||||
const Ogre::Vector3 &value)
|
||||
{
|
||||
GoapBlackboard params;
|
||||
params.setVec3Value(paramName, value);
|
||||
send(eventName, params);
|
||||
}
|
||||
66
src/features/editScene/systems/EventBus.hpp
Normal file
66
src/features/editScene/systems/EventBus.hpp
Normal file
@@ -0,0 +1,66 @@
|
||||
#ifndef EDITSCENE_EVENT_BUS_HPP
|
||||
#define EDITSCENE_EVENT_BUS_HPP
|
||||
#pragma once
|
||||
|
||||
#include "../components/GoapBlackboard.hpp"
|
||||
#include <Ogre.h>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* Global synchronous event bus.
|
||||
*
|
||||
* Subscribers register callbacks by event name. When an event is sent,
|
||||
* all matching callbacks are invoked immediately in the send() call.
|
||||
*
|
||||
* Payload is a GoapBlackboard — reuses the existing int/float/vec3/bit/string
|
||||
* storage with zero RTTI overhead.
|
||||
*/
|
||||
class EventBus {
|
||||
public:
|
||||
using ListenerId = uint64_t;
|
||||
using Callback =
|
||||
std::function<void(const Ogre::String &eventName,
|
||||
const GoapBlackboard ¶ms)>;
|
||||
|
||||
static EventBus &getInstance();
|
||||
|
||||
/** Subscribe to an event. Returns a handle for unsubscribe(). */
|
||||
ListenerId subscribe(const Ogre::String &eventName, Callback cb);
|
||||
|
||||
/** Unsubscribe by handle. Safe to call with invalid id. */
|
||||
void unsubscribe(ListenerId id);
|
||||
|
||||
/** Send an event with a GoapBlackboard payload. */
|
||||
void send(const Ogre::String &eventName,
|
||||
const GoapBlackboard ¶ms = {});
|
||||
|
||||
/** Convenience: send event with a single int param. */
|
||||
void send(const Ogre::String &eventName,
|
||||
const Ogre::String ¶mName, int value);
|
||||
|
||||
/** Convenience: send event with a single float param. */
|
||||
void send(const Ogre::String &eventName,
|
||||
const Ogre::String ¶mName, float value);
|
||||
|
||||
/** Convenience: send event with a single Vec3 param. */
|
||||
void send(const Ogre::String &eventName,
|
||||
const Ogre::String ¶mName,
|
||||
const Ogre::Vector3 &value);
|
||||
|
||||
private:
|
||||
EventBus() = default;
|
||||
|
||||
struct Listener {
|
||||
ListenerId id;
|
||||
Callback callback;
|
||||
};
|
||||
|
||||
ListenerId m_nextId = 1;
|
||||
std::unordered_map<Ogre::String, std::vector<Listener>> m_listeners;
|
||||
std::unordered_map<ListenerId, Ogre::String> m_idToEvent;
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_EVENT_BUS_HPP
|
||||
226
src/features/editScene/systems/EventHandlerSystem.cpp
Normal file
226
src/features/editScene/systems/EventHandlerSystem.cpp
Normal file
@@ -0,0 +1,226 @@
|
||||
#include "EventHandlerSystem.hpp"
|
||||
#include "BehaviorTreeSystem.hpp"
|
||||
#include "../components/EventHandler.hpp"
|
||||
#include "../components/ActionDatabase.hpp"
|
||||
#include "../components/GoapBlackboard.hpp"
|
||||
#include <OgreLogManager.h>
|
||||
|
||||
EventHandlerSystem::EventHandlerSystem(flecs::world &world,
|
||||
BehaviorTreeSystem *btSystem)
|
||||
: m_world(world)
|
||||
, m_btSystem(btSystem)
|
||||
{
|
||||
}
|
||||
|
||||
EventHandlerSystem::~EventHandlerSystem()
|
||||
{
|
||||
// Unsubscribe all remaining listeners
|
||||
for (auto &pair : m_subscriptions) {
|
||||
EventBus::getInstance().unsubscribe(pair.second);
|
||||
}
|
||||
m_subscriptions.clear();
|
||||
}
|
||||
|
||||
void EventHandlerSystem::subscribeEntity(
|
||||
flecs::entity e, const EventHandlerComponent &handler)
|
||||
{
|
||||
if (!handler.enabled || handler.eventName.empty())
|
||||
return;
|
||||
|
||||
flecs::entity_t id = e.id();
|
||||
if (m_subscriptions.find(id) != m_subscriptions.end())
|
||||
return;
|
||||
|
||||
EventBus::ListenerId lid = EventBus::getInstance().subscribe(
|
||||
handler.eventName,
|
||||
[this, id, handler](const Ogre::String &,
|
||||
const GoapBlackboard ¶ms) {
|
||||
this->onEvent(id, handler.eventName, params);
|
||||
});
|
||||
|
||||
m_subscriptions[id] = lid;
|
||||
}
|
||||
|
||||
void EventHandlerSystem::unsubscribeEntity(flecs::entity_t id)
|
||||
{
|
||||
auto it = m_subscriptions.find(id);
|
||||
if (it == m_subscriptions.end())
|
||||
return;
|
||||
|
||||
EventBus::getInstance().unsubscribe(it->second);
|
||||
m_subscriptions.erase(it);
|
||||
|
||||
// If there is an active handler, abort it and clean up
|
||||
auto activeIt = m_activeHandlers.find(id);
|
||||
if (activeIt != m_activeHandlers.end()) {
|
||||
auto keysIt = m_injectedKeys.find(id);
|
||||
if (keysIt != m_injectedKeys.end()) {
|
||||
flecs::entity e = m_world.entity(id);
|
||||
if (e.is_alive())
|
||||
removeParams(e, keysIt->second);
|
||||
m_injectedKeys.erase(keysIt);
|
||||
}
|
||||
m_activeHandlers.erase(activeIt);
|
||||
}
|
||||
}
|
||||
|
||||
void EventHandlerSystem::onEvent(flecs::entity_t entityId,
|
||||
const Ogre::String &eventName,
|
||||
const GoapBlackboard ¶ms)
|
||||
{
|
||||
(void)eventName;
|
||||
|
||||
flecs::entity e = m_world.entity(entityId);
|
||||
if (!e.is_alive() || !e.has<EventHandlerComponent>())
|
||||
return;
|
||||
|
||||
auto &handler = e.get<EventHandlerComponent>();
|
||||
if (!handler.enabled || handler.actionName.empty())
|
||||
return;
|
||||
|
||||
// If already handling an event, abort the previous one first
|
||||
auto activeIt = m_activeHandlers.find(entityId);
|
||||
if (activeIt != m_activeHandlers.end()) {
|
||||
auto keysIt = m_injectedKeys.find(entityId);
|
||||
if (keysIt != m_injectedKeys.end()) {
|
||||
removeParams(e, keysIt->second);
|
||||
m_injectedKeys.erase(keysIt);
|
||||
}
|
||||
m_activeHandlers.erase(activeIt);
|
||||
}
|
||||
|
||||
// Start new handler
|
||||
ActiveHandler ah;
|
||||
ah.entityId = entityId;
|
||||
ah.actionName = handler.actionName;
|
||||
ah.eventParams = params;
|
||||
ah.firstFrame = true;
|
||||
m_activeHandlers[entityId] = ah;
|
||||
|
||||
// Inject params immediately so the first update() tick sees them
|
||||
std::unordered_set<std::string> injected;
|
||||
injectParams(e, params, injected);
|
||||
m_injectedKeys[entityId] = std::move(injected);
|
||||
}
|
||||
|
||||
void EventHandlerSystem::injectParams(
|
||||
flecs::entity e, const GoapBlackboard ¶ms,
|
||||
std::unordered_set<std::string> &outKeys)
|
||||
{
|
||||
if (!e.has<GoapBlackboard>())
|
||||
e.set<GoapBlackboard>({});
|
||||
|
||||
auto &bb = e.get_mut<GoapBlackboard>();
|
||||
|
||||
for (const auto &pair : params.values) {
|
||||
bb.setValue(pair.first, pair.second);
|
||||
outKeys.insert(pair.first);
|
||||
}
|
||||
for (const auto &pair : params.floatValues) {
|
||||
bb.setFloatValue(pair.first, pair.second);
|
||||
outKeys.insert(pair.first);
|
||||
}
|
||||
for (const auto &pair : params.vec3Values) {
|
||||
bb.setVec3Value(pair.first, pair.second);
|
||||
outKeys.insert(pair.first);
|
||||
}
|
||||
for (const auto &pair : params.stringValues) {
|
||||
bb.setStringValue(pair.first, pair.second);
|
||||
outKeys.insert(pair.first);
|
||||
}
|
||||
// Bits are not injected individually; they are part of the event
|
||||
// payload semantics but merging bits globally is risky.
|
||||
// Instead, event bits are NOT auto-injected. If needed, use
|
||||
// setBit nodes inside the handler BT.
|
||||
}
|
||||
|
||||
void EventHandlerSystem::removeParams(
|
||||
flecs::entity e,
|
||||
const std::unordered_set<std::string> &keys)
|
||||
{
|
||||
if (!e.has<GoapBlackboard>())
|
||||
return;
|
||||
|
||||
auto &bb = e.get_mut<GoapBlackboard>();
|
||||
for (const auto &key : keys) {
|
||||
bb.removeValue(key);
|
||||
bb.removeFloatValue(key);
|
||||
bb.removeVec3Value(key);
|
||||
bb.removeStringValue(key);
|
||||
}
|
||||
}
|
||||
|
||||
void EventHandlerSystem::update(float deltaTime)
|
||||
{
|
||||
if (!m_btSystem)
|
||||
return;
|
||||
|
||||
// --- Sync subscriptions with current entities ---
|
||||
std::unordered_set<flecs::entity_t> currentEntities;
|
||||
m_world.query<EventHandlerComponent>().each(
|
||||
[&](flecs::entity e, EventHandlerComponent &handler) {
|
||||
currentEntities.insert(e.id());
|
||||
if (handler.enabled) {
|
||||
subscribeEntity(e, handler);
|
||||
} else {
|
||||
unsubscribeEntity(e.id());
|
||||
}
|
||||
});
|
||||
|
||||
// Unsubscribe entities that lost their component
|
||||
std::vector<flecs::entity_t> toRemove;
|
||||
for (auto &pair : m_subscriptions) {
|
||||
if (currentEntities.find(pair.first) == currentEntities.end())
|
||||
toRemove.push_back(pair.first);
|
||||
}
|
||||
for (flecs::entity_t id : toRemove)
|
||||
unsubscribeEntity(id);
|
||||
|
||||
// --- Tick active handlers ---
|
||||
std::vector<flecs::entity_t> completedHandlers;
|
||||
for (auto &pair : m_activeHandlers) {
|
||||
flecs::entity_t id = pair.first;
|
||||
ActiveHandler &ah = pair.second;
|
||||
|
||||
// Look up the action in the database
|
||||
ActionDatabase *db = nullptr;
|
||||
m_world.query<ActionDatabase>().each(
|
||||
[&](flecs::entity, ActionDatabase &database) {
|
||||
if (!db)
|
||||
db = &database;
|
||||
});
|
||||
if (!db) {
|
||||
completedHandlers.push_back(id);
|
||||
continue;
|
||||
}
|
||||
|
||||
const GoapAction *action = db->findAction(ah.actionName);
|
||||
if (!action) {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"[EventHandlerSystem] Action not found: " +
|
||||
ah.actionName);
|
||||
completedHandlers.push_back(id);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto status = m_btSystem->evaluatePlayerAction(
|
||||
id, action->behaviorTree, deltaTime, ah.firstFrame);
|
||||
ah.firstFrame = false;
|
||||
|
||||
if (status != BehaviorTreeSystem::Status::running) {
|
||||
completedHandlers.push_back(id);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Clean up completed handlers ---
|
||||
for (flecs::entity_t id : completedHandlers) {
|
||||
flecs::entity e = m_world.entity(id);
|
||||
auto keysIt = m_injectedKeys.find(id);
|
||||
if (keysIt != m_injectedKeys.end()) {
|
||||
if (e.is_alive())
|
||||
removeParams(e, keysIt->second);
|
||||
m_injectedKeys.erase(keysIt);
|
||||
}
|
||||
m_activeHandlers.erase(id);
|
||||
}
|
||||
}
|
||||
64
src/features/editScene/systems/EventHandlerSystem.hpp
Normal file
64
src/features/editScene/systems/EventHandlerSystem.hpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#ifndef EDITSCENE_EVENT_HANDLER_SYSTEM_HPP
|
||||
#define EDITSCENE_EVENT_HANDLER_SYSTEM_HPP
|
||||
#pragma once
|
||||
|
||||
#include <flecs.h>
|
||||
#include <Ogre.h>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "EventBus.hpp"
|
||||
#include "../components/GoapBlackboard.hpp"
|
||||
|
||||
class BehaviorTreeSystem;
|
||||
class EditorApp;
|
||||
|
||||
/**
|
||||
* System that executes behavior trees in response to events.
|
||||
*
|
||||
* For each entity with EventHandlerComponent, subscribes to the
|
||||
* specified event. When the event fires, copies parameters into the
|
||||
* entity's GoapBlackboard and runs the referenced action's behavior
|
||||
* tree. Cleans up injected parameters when the tree completes.
|
||||
*/
|
||||
class EventHandlerSystem {
|
||||
public:
|
||||
EventHandlerSystem(flecs::world &world, BehaviorTreeSystem *btSystem);
|
||||
~EventHandlerSystem();
|
||||
|
||||
void update(float deltaTime);
|
||||
|
||||
private:
|
||||
struct ActiveHandler {
|
||||
flecs::entity_t entityId;
|
||||
Ogre::String actionName;
|
||||
GoapBlackboard eventParams;
|
||||
bool firstFrame = true;
|
||||
};
|
||||
|
||||
void subscribeEntity(flecs::entity e,
|
||||
const class EventHandlerComponent &handler);
|
||||
void unsubscribeEntity(flecs::entity_t id);
|
||||
void onEvent(flecs::entity_t entityId,
|
||||
const Ogre::String &eventName,
|
||||
const GoapBlackboard ¶ms);
|
||||
void injectParams(flecs::entity e, const GoapBlackboard ¶ms,
|
||||
std::unordered_set<std::string> &outKeys);
|
||||
void removeParams(flecs::entity e,
|
||||
const std::unordered_set<std::string> &keys);
|
||||
|
||||
flecs::world &m_world;
|
||||
BehaviorTreeSystem *m_btSystem;
|
||||
|
||||
// Per-entity event subscription
|
||||
std::unordered_map<flecs::entity_t, EventBus::ListenerId> m_subscriptions;
|
||||
|
||||
// Per-entity active handler (one at a time per entity)
|
||||
std::unordered_map<flecs::entity_t, ActiveHandler> m_activeHandlers;
|
||||
|
||||
// Keys injected into blackboard per active handler
|
||||
std::unordered_map<flecs::entity_t, std::unordered_set<std::string>> m_injectedKeys;
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_EVENT_HANDLER_SYSTEM_HPP
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "../components/WaterPlane.hpp"
|
||||
#include "../components/Sun.hpp"
|
||||
#include "../components/Skybox.hpp"
|
||||
#include "../components/EventHandler.hpp"
|
||||
#include "../components/ActionDatabase.hpp"
|
||||
#include "../components/ActionDebug.hpp"
|
||||
#include "../components/SmartObject.hpp"
|
||||
@@ -307,6 +308,9 @@ nlohmann::json SceneSerializer::serializeEntity(flecs::entity entity)
|
||||
if (entity.has<ActuatorComponent>()) {
|
||||
json["actuator"] = serializeActuator(entity);
|
||||
}
|
||||
if (entity.has<EventHandlerComponent>()) {
|
||||
json["eventHandler"] = serializeEventHandler(entity);
|
||||
}
|
||||
if (entity.has<GoapPlannerComponent>()) {
|
||||
json["goapPlanner"] = serializeGoapPlanner(entity);
|
||||
}
|
||||
@@ -516,6 +520,9 @@ void SceneSerializer::deserializeEntity(const nlohmann::json &json,
|
||||
if (json.contains("actuator")) {
|
||||
deserializeActuator(entity, json["actuator"]);
|
||||
}
|
||||
if (json.contains("eventHandler")) {
|
||||
deserializeEventHandler(entity, json["eventHandler"]);
|
||||
}
|
||||
if (json.contains("goapPlanner")) {
|
||||
deserializeGoapPlanner(entity, json["goapPlanner"]);
|
||||
}
|
||||
@@ -800,6 +807,9 @@ void SceneSerializer::deserializeEntityComponents(
|
||||
}
|
||||
if (json.contains("actuator")) {
|
||||
deserializeActuator(entity, json["actuator"]);
|
||||
if (json.contains("eventHandler")) {
|
||||
deserializeEventHandler(entity, json["eventHandler"]);
|
||||
}
|
||||
}
|
||||
if (json.contains("goapPlanner")) {
|
||||
deserializeGoapPlanner(entity, json["goapPlanner"]);
|
||||
@@ -3700,3 +3710,24 @@ void SceneSerializer::deserializeActuator(flecs::entity entity,
|
||||
}
|
||||
entity.set<ActuatorComponent>(actuator);
|
||||
}
|
||||
|
||||
|
||||
nlohmann::json SceneSerializer::serializeEventHandler(flecs::entity entity)
|
||||
{
|
||||
const EventHandlerComponent &handler = entity.get<EventHandlerComponent>();
|
||||
nlohmann::json json;
|
||||
json["eventName"] = handler.eventName;
|
||||
json["actionName"] = handler.actionName;
|
||||
json["enabled"] = handler.enabled;
|
||||
return json;
|
||||
}
|
||||
|
||||
void SceneSerializer::deserializeEventHandler(flecs::entity entity,
|
||||
const nlohmann::json &json)
|
||||
{
|
||||
EventHandlerComponent handler;
|
||||
handler.eventName = json.value("eventName", handler.eventName);
|
||||
handler.actionName = json.value("actionName", handler.actionName);
|
||||
handler.enabled = json.value("enabled", handler.enabled);
|
||||
entity.set<EventHandlerComponent>(handler);
|
||||
}
|
||||
|
||||
@@ -211,6 +211,7 @@ private:
|
||||
nlohmann::json serializePathFollowing(flecs::entity entity);
|
||||
nlohmann::json serializeSmartObject(flecs::entity entity);
|
||||
nlohmann::json serializeActuator(flecs::entity entity);
|
||||
nlohmann::json serializeEventHandler(flecs::entity entity);
|
||||
nlohmann::json serializeGoapPlanner(flecs::entity entity);
|
||||
nlohmann::json serializeBehaviorTree(flecs::entity entity);
|
||||
void deserializeActionDatabase(flecs::entity entity,
|
||||
@@ -223,6 +224,8 @@ private:
|
||||
const nlohmann::json &json);
|
||||
void deserializeActuator(flecs::entity entity,
|
||||
const nlohmann::json &json);
|
||||
void deserializeEventHandler(flecs::entity entity,
|
||||
const nlohmann::json &json);
|
||||
void deserializeGoapPlanner(flecs::entity entity,
|
||||
const nlohmann::json &json);
|
||||
void deserializeBehaviorTree(flecs::entity entity,
|
||||
|
||||
64
src/features/editScene/ui/EventHandlerEditor.cpp
Normal file
64
src/features/editScene/ui/EventHandlerEditor.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#include "EventHandlerEditor.hpp"
|
||||
#include "../components/ActionDatabase.hpp"
|
||||
#include <imgui.h>
|
||||
|
||||
static ActionDatabase *findDatabase(flecs::entity entity)
|
||||
{
|
||||
auto world = entity.world();
|
||||
ActionDatabase *db = nullptr;
|
||||
world.query<ActionDatabase>().each(
|
||||
[&](flecs::entity, ActionDatabase &database) {
|
||||
if (!db)
|
||||
db = &database;
|
||||
});
|
||||
return db;
|
||||
}
|
||||
|
||||
bool EventHandlerEditor::renderComponent(flecs::entity entity,
|
||||
EventHandlerComponent &handler)
|
||||
{
|
||||
bool modified = false;
|
||||
ImGui::PushID("EventHandler");
|
||||
|
||||
char eventBuf[256];
|
||||
snprintf(eventBuf, sizeof(eventBuf), "%s", handler.eventName.c_str());
|
||||
if (ImGui::InputText("Event Name", eventBuf, sizeof(eventBuf))) {
|
||||
handler.eventName = eventBuf;
|
||||
modified = true;
|
||||
}
|
||||
|
||||
if (ImGui::Checkbox("Enabled", &handler.enabled))
|
||||
modified = true;
|
||||
|
||||
// Action selection from database
|
||||
ActionDatabase *db = findDatabase(entity);
|
||||
if (db && !db->actions.empty()) {
|
||||
int selected = -1;
|
||||
std::vector<const char *> names;
|
||||
for (size_t i = 0; i < db->actions.size(); i++) {
|
||||
names.push_back(db->actions[i].name.c_str());
|
||||
if (handler.actionName == db->actions[i].name)
|
||||
selected = static_cast<int>(i);
|
||||
}
|
||||
if (ImGui::Combo("Action", &selected, names.data(),
|
||||
static_cast<int>(names.size()))) {
|
||||
if (selected >= 0 &&
|
||||
selected < static_cast<int>(names.size()))
|
||||
handler.actionName = names[selected];
|
||||
modified = true;
|
||||
}
|
||||
} else {
|
||||
ImGui::TextDisabled("No actions in database");
|
||||
char actionBuf[256];
|
||||
snprintf(actionBuf, sizeof(actionBuf), "%s",
|
||||
handler.actionName.c_str());
|
||||
if (ImGui::InputText("Action Name", actionBuf,
|
||||
sizeof(actionBuf))) {
|
||||
handler.actionName = actionBuf;
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
return modified;
|
||||
}
|
||||
20
src/features/editScene/ui/EventHandlerEditor.hpp
Normal file
20
src/features/editScene/ui/EventHandlerEditor.hpp
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifndef EDITSCENE_EVENT_HANDLER_EDITOR_HPP
|
||||
#define EDITSCENE_EVENT_HANDLER_EDITOR_HPP
|
||||
#pragma once
|
||||
|
||||
#include "ComponentEditor.hpp"
|
||||
#include "../components/EventHandler.hpp"
|
||||
|
||||
class EventHandlerEditor : public ComponentEditor<EventHandlerComponent> {
|
||||
public:
|
||||
const char *getName() const override
|
||||
{
|
||||
return "Event Handler";
|
||||
}
|
||||
|
||||
protected:
|
||||
bool renderComponent(flecs::entity entity,
|
||||
EventHandlerComponent &handler) override;
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_EVENT_HANDLER_EDITOR_HPP
|
||||
Reference in New Issue
Block a user