diff options
author | Kelly Rauchenberger <fefferburbia@gmail.com> | 2018-05-14 18:40:54 -0400 |
---|---|---|
committer | Kelly Rauchenberger <fefferburbia@gmail.com> | 2018-05-17 15:39:39 -0400 |
commit | 5e48cf6333aca7af6854d79194f138d57ce0b5e1 (patch) | |
tree | b1703c6268b762a04a101f35188163fe2d9feb45 | |
parent | 046ee24a341468e9b3ea2983a731dbce18b52ac6 (diff) | |
download | therapy-5e48cf6333aca7af6854d79194f138d57ce0b5e1.tar.gz therapy-5e48cf6333aca7af6854d79194f138d57ce0b5e1.tar.bz2 therapy-5e48cf6333aca7af6854d79194f138d57ce0b5e1.zip |
Specialized treatment of behavior scripts
The AutomatableComponent now links to the Runnable entity representing the behavior script. Also reordered the SystemManager and EntityManager members of the Game class such that the EntityManager is destroyed before the SystemManager is. This fixes a bug where the destruction of a component has some affect on the state of a system. Specifically, if the ScriptingSystem (and thus the Lua state) is destroyed before the EntityManager is and there are any Runnable entities, the game will crash when trying to destroy them.
-rw-r--r-- | src/components/automatable.h | 34 | ||||
-rw-r--r-- | src/components/prototypable.h | 11 | ||||
-rw-r--r-- | src/components/runnable.h | 36 | ||||
-rw-r--r-- | src/game.h | 2 | ||||
-rw-r--r-- | src/systems/realizing.cpp | 33 | ||||
-rw-r--r-- | src/systems/scripting.cpp | 70 | ||||
-rw-r--r-- | src/systems/scripting.h | 10 |
7 files changed, 153 insertions, 43 deletions
diff --git a/src/components/automatable.h b/src/components/automatable.h new file mode 100644 index 0000000..22d9859 --- /dev/null +++ b/src/components/automatable.h | |||
@@ -0,0 +1,34 @@ | |||
1 | #ifndef AUTOMATABLE_H_FACB42A5 | ||
2 | #define AUTOMATABLE_H_FACB42A5 | ||
3 | |||
4 | #include "component.h" | ||
5 | #include "entity_manager.h" | ||
6 | |||
7 | class AutomatableComponent : public Component { | ||
8 | public: | ||
9 | |||
10 | using id_type = EntityManager::id_type; | ||
11 | |||
12 | /** | ||
13 | * Controls what script will be run as this entity's behavior. It should refer | ||
14 | * to a table in the global namespace of the script engine state, and that | ||
15 | * table should contain a function called "Behavior". | ||
16 | */ | ||
17 | std::string table; | ||
18 | |||
19 | /** | ||
20 | * Whether or not the behavior script is running. | ||
21 | * | ||
22 | * @managed_by ScriptingSystem | ||
23 | */ | ||
24 | bool running = false; | ||
25 | |||
26 | /** | ||
27 | * The entity ID of the running script, if there is one. | ||
28 | * | ||
29 | * @managed_by ScriptingSystem | ||
30 | */ | ||
31 | id_type script; | ||
32 | }; | ||
33 | |||
34 | #endif /* end of include guard: AUTOMATABLE_H_FACB42A5 */ | ||
diff --git a/src/components/prototypable.h b/src/components/prototypable.h index 4659e7c..c0dd972 100644 --- a/src/components/prototypable.h +++ b/src/components/prototypable.h | |||
@@ -9,14 +9,15 @@ public: | |||
9 | 9 | ||
10 | using id_type = EntityManager::id_type; | 10 | using id_type = EntityManager::id_type; |
11 | 11 | ||
12 | /** | ||
13 | * The index of the object in the map definition. | ||
14 | */ | ||
12 | size_t mapObjectIndex; | 15 | size_t mapObjectIndex; |
13 | 16 | ||
17 | /** | ||
18 | * The name of the prototype that the object was spawned from. | ||
19 | */ | ||
14 | std::string prototypeId; | 20 | std::string prototypeId; |
15 | |||
16 | bool hasBehavior = false; | ||
17 | bool runningBehavior = false; | ||
18 | |||
19 | id_type behaviorScript; | ||
20 | }; | 21 | }; |
21 | 22 | ||
22 | #endif /* end of include guard: PROTOTYPABLE_H_817F2205 */ | 23 | #endif /* end of include guard: PROTOTYPABLE_H_817F2205 */ |
diff --git a/src/components/runnable.h b/src/components/runnable.h index 1b994fb..956bfdc 100644 --- a/src/components/runnable.h +++ b/src/components/runnable.h | |||
@@ -4,12 +4,48 @@ | |||
4 | #include "component.h" | 4 | #include "component.h" |
5 | #include <sol.hpp> | 5 | #include <sol.hpp> |
6 | #include <memory> | 6 | #include <memory> |
7 | #include "entity_manager.h" | ||
7 | 8 | ||
8 | class RunnableComponent : public Component { | 9 | class RunnableComponent : public Component { |
9 | public: | 10 | public: |
10 | 11 | ||
12 | using id_type = EntityManager::id_type; | ||
13 | |||
14 | /** | ||
15 | * A Lua stack where the entity's script is running. | ||
16 | * | ||
17 | * NOTE: This object is called a thread, but there is no multi-threading going | ||
18 | * on. | ||
19 | * | ||
20 | * @managed_by ScriptingSystem | ||
21 | */ | ||
11 | std::unique_ptr<sol::thread> runner; | 22 | std::unique_ptr<sol::thread> runner; |
23 | |||
24 | /** | ||
25 | * An entry point to the script running in the runner thread. | ||
26 | * | ||
27 | * @managed_by ScriptingSystem | ||
28 | */ | ||
12 | std::unique_ptr<sol::coroutine> callable; | 29 | std::unique_ptr<sol::coroutine> callable; |
30 | |||
31 | /** | ||
32 | * Whether or not this entity represents a behavior script. A behavior script | ||
33 | * usually does not terminate on its own, and can be terminated at will by | ||
34 | * another system, usually when the automatable entity leaves the active map. | ||
35 | * | ||
36 | * @managed_by ScriptingSystem | ||
37 | */ | ||
38 | bool behavior = false; | ||
39 | |||
40 | /** | ||
41 | * If this is a behavior script, this is the ID of the automatable entity that | ||
42 | * the behavior belongs to. This is required so that the ScriptingSystem can | ||
43 | * notify the automatable entity if the behavior script terminates by itself, | ||
44 | * and that it shouldn't attempt to terminate it. | ||
45 | * | ||
46 | * @managed_by ScriptingSystem | ||
47 | */ | ||
48 | id_type actor; | ||
13 | }; | 49 | }; |
14 | 50 | ||
15 | #endif /* end of include guard: AUTOMATABLE_H_3D519131 */ | 51 | #endif /* end of include guard: AUTOMATABLE_H_3D519131 */ |
diff --git a/src/game.h b/src/game.h index dc256c6..d7fdcd7 100644 --- a/src/game.h +++ b/src/game.h | |||
@@ -44,8 +44,8 @@ private: | |||
44 | 44 | ||
45 | std::mt19937 rng_; | 45 | std::mt19937 rng_; |
46 | Renderer renderer_; | 46 | Renderer renderer_; |
47 | EntityManager entityManager_; | ||
48 | SystemManager systemManager_; | 47 | SystemManager systemManager_; |
48 | EntityManager entityManager_; | ||
49 | bool shouldQuit_ = false; | 49 | bool shouldQuit_ = false; |
50 | }; | 50 | }; |
51 | 51 | ||
diff --git a/src/systems/realizing.cpp b/src/systems/realizing.cpp index 7f5aefb..baacf5a 100644 --- a/src/systems/realizing.cpp +++ b/src/systems/realizing.cpp | |||
@@ -12,6 +12,7 @@ | |||
12 | #include "components/ponderable.h" | 12 | #include "components/ponderable.h" |
13 | #include "components/transformable.h" | 13 | #include "components/transformable.h" |
14 | #include "components/prototypable.h" | 14 | #include "components/prototypable.h" |
15 | #include "components/automatable.h" | ||
15 | #include "systems/mapping.h" | 16 | #include "systems/mapping.h" |
16 | #include "systems/animating.h" | 17 | #include "systems/animating.h" |
17 | #include "systems/pondering.h" | 18 | #include "systems/pondering.h" |
@@ -223,7 +224,10 @@ RealizingSystem::RealizingSystem( | |||
223 | 224 | ||
224 | if (prototypeId == "movplat") | 225 | if (prototypeId == "movplat") |
225 | { | 226 | { |
226 | prototypable.hasBehavior = true; | 227 | auto& automatable = game_.getEntityManager(). |
228 | emplaceComponent<AutomatableComponent>(mapObject); | ||
229 | |||
230 | automatable.table = prototypeId; | ||
227 | } else if (prototypeId == "checkpoint") | 231 | } else if (prototypeId == "checkpoint") |
228 | { | 232 | { |
229 | auto& ponderable = game_.getEntityManager(). | 233 | auto& ponderable = game_.getEntityManager(). |
@@ -403,19 +407,9 @@ void RealizingSystem::enterActiveMap(id_type entity) | |||
403 | ponderable.active = true; | 407 | ponderable.active = true; |
404 | } | 408 | } |
405 | 409 | ||
406 | if (game_.getEntityManager().hasComponent<PrototypableComponent>(entity)) | 410 | if (game_.getEntityManager().hasComponent<AutomatableComponent>(entity)) |
407 | { | 411 | { |
408 | auto& prototypable = game_.getEntityManager(). | 412 | game_.getSystemManager().getSystem<ScriptingSystem>().startBehavior(entity); |
409 | getComponent<PrototypableComponent>(entity); | ||
410 | |||
411 | if (prototypable.hasBehavior) | ||
412 | { | ||
413 | auto& scripting = game_.getSystemManager().getSystem<ScriptingSystem>(); | ||
414 | |||
415 | prototypable.hasBehavior = true; | ||
416 | prototypable.runningBehavior = true; | ||
417 | prototypable.behaviorScript = scripting.runBehaviorScript(entity); | ||
418 | } | ||
419 | } | 413 | } |
420 | } | 414 | } |
421 | 415 | ||
@@ -437,17 +431,8 @@ void RealizingSystem::leaveActiveMap(id_type entity) | |||
437 | ponderable.active = false; | 431 | ponderable.active = false; |
438 | } | 432 | } |
439 | 433 | ||
440 | if (game_.getEntityManager().hasComponent<PrototypableComponent>(entity)) | 434 | if (game_.getEntityManager().hasComponent<AutomatableComponent>(entity)) |
441 | { | 435 | { |
442 | auto& prototypable = game_.getEntityManager(). | 436 | game_.getSystemManager().getSystem<ScriptingSystem>().stopBehavior(entity); |
443 | getComponent<PrototypableComponent>(entity); | ||
444 | |||
445 | if (prototypable.runningBehavior) | ||
446 | { | ||
447 | auto& scripting = game_.getSystemManager().getSystem<ScriptingSystem>(); | ||
448 | scripting.killScript(prototypable.behaviorScript); | ||
449 | |||
450 | prototypable.runningBehavior = false; | ||
451 | } | ||
452 | } | 437 | } |
453 | } | 438 | } |
diff --git a/src/systems/scripting.cpp b/src/systems/scripting.cpp index 57c3fd5..c423558 100644 --- a/src/systems/scripting.cpp +++ b/src/systems/scripting.cpp | |||
@@ -6,6 +6,7 @@ | |||
6 | #include "components/playable.h" | 6 | #include "components/playable.h" |
7 | #include "components/mappable.h" | 7 | #include "components/mappable.h" |
8 | #include "components/prototypable.h" | 8 | #include "components/prototypable.h" |
9 | #include "components/automatable.h" | ||
9 | #include "systems/realizing.h" | 10 | #include "systems/realizing.h" |
10 | #include "vector.h" | 11 | #include "vector.h" |
11 | 12 | ||
@@ -133,28 +134,34 @@ void ScriptingSystem::tick(double dt) | |||
133 | 134 | ||
134 | if (!*runnable.callable) | 135 | if (!*runnable.callable) |
135 | { | 136 | { |
136 | game_.getEntityManager().deleteEntity(entity); | 137 | killScript(entity); |
137 | } | 138 | } |
138 | } | 139 | } |
139 | } | 140 | } |
140 | 141 | ||
141 | void ScriptingSystem::killScript(id_type entity) | 142 | void ScriptingSystem::killScript(id_type entity) |
142 | { | 143 | { |
143 | if (game_.getEntityManager().hasComponent<RunnableComponent>(entity)) | 144 | auto& runnable = game_.getEntityManager(). |
145 | getComponent<RunnableComponent>(entity); | ||
146 | |||
147 | if (runnable.behavior) | ||
144 | { | 148 | { |
145 | game_.getEntityManager().deleteEntity(entity); | 149 | auto& automatable = game_.getEntityManager(). |
150 | getComponent<AutomatableComponent>(runnable.actor); | ||
151 | |||
152 | automatable.running = false; | ||
146 | } | 153 | } |
154 | |||
155 | game_.getEntityManager().deleteEntity(entity); | ||
147 | } | 156 | } |
148 | 157 | ||
149 | template <typename... Args> | 158 | template <typename... Args> |
150 | EntityManager::id_type ScriptingSystem::runScript( | 159 | sol::optional<EntityManager::id_type> ScriptingSystem::runScript( |
160 | std::string table, | ||
151 | std::string event, | 161 | std::string event, |
152 | id_type entity, | 162 | id_type entity, |
153 | Args&&... args) | 163 | Args&&... args) |
154 | { | 164 | { |
155 | auto& prototypable = game_.getEntityManager(). | ||
156 | getComponent<PrototypableComponent>(entity); | ||
157 | |||
158 | id_type script = game_.getEntityManager().emplaceEntity(); | 165 | id_type script = game_.getEntityManager().emplaceEntity(); |
159 | 166 | ||
160 | auto& runnable = game_.getEntityManager(). | 167 | auto& runnable = game_.getEntityManager(). |
@@ -171,7 +178,7 @@ EntityManager::id_type ScriptingSystem::runScript( | |||
171 | new sol::coroutine( | 178 | new sol::coroutine( |
172 | runnable.runner->state(). | 179 | runnable.runner->state(). |
173 | traverse_get<sol::function>( | 180 | traverse_get<sol::function>( |
174 | prototypable.prototypeId, | 181 | table, |
175 | event))); | 182 | event))); |
176 | 183 | ||
177 | if (!*runnable.callable) | 184 | if (!*runnable.callable) |
@@ -189,17 +196,58 @@ EntityManager::id_type ScriptingSystem::runScript( | |||
189 | throw std::runtime_error(e.what()); | 196 | throw std::runtime_error(e.what()); |
190 | } | 197 | } |
191 | 198 | ||
192 | return script; | 199 | if (*runnable.callable) |
200 | { | ||
201 | return { script }; | ||
202 | } else { | ||
203 | killScript(script); | ||
204 | |||
205 | return {}; | ||
206 | } | ||
207 | } | ||
208 | |||
209 | void ScriptingSystem::startBehavior(id_type entity) | ||
210 | { | ||
211 | auto& automatable = game_.getEntityManager(). | ||
212 | getComponent<AutomatableComponent>(entity); | ||
213 | |||
214 | sol::optional<id_type> script = | ||
215 | runScript( | ||
216 | automatable.table, | ||
217 | "Behavior", | ||
218 | entity); | ||
219 | |||
220 | if (script) | ||
221 | { | ||
222 | automatable.script = *script; | ||
223 | automatable.running = true; | ||
224 | |||
225 | auto& runnable = game_.getEntityManager(). | ||
226 | getComponent<RunnableComponent>(automatable.script); | ||
227 | |||
228 | runnable.behavior = true; | ||
229 | runnable.actor = entity; | ||
230 | } | ||
193 | } | 231 | } |
194 | 232 | ||
195 | EntityManager::id_type ScriptingSystem::runBehaviorScript(id_type entity) | 233 | void ScriptingSystem::stopBehavior(id_type entity) |
196 | { | 234 | { |
197 | return runScript("Behavior", entity); | 235 | auto& automatable = game_.getEntityManager(). |
236 | getComponent<AutomatableComponent>(entity); | ||
237 | |||
238 | if (automatable.running) | ||
239 | { | ||
240 | killScript(automatable.script); | ||
241 | } | ||
198 | } | 242 | } |
199 | 243 | ||
200 | void ScriptingSystem::onTouch(id_type entity, id_type player) | 244 | void ScriptingSystem::onTouch(id_type entity, id_type player) |
201 | { | 245 | { |
246 | auto& prototypable = game_.getEntityManager(). | ||
247 | getComponent<PrototypableComponent>(entity); | ||
248 | |||
202 | runScript( | 249 | runScript( |
250 | prototypable.prototypeId, | ||
203 | "OnTouch", | 251 | "OnTouch", |
204 | entity, | 252 | entity, |
205 | script_entity(player)); | 253 | script_entity(player)); |
diff --git a/src/systems/scripting.h b/src/systems/scripting.h index e330316..b119c3f 100644 --- a/src/systems/scripting.h +++ b/src/systems/scripting.h | |||
@@ -13,14 +13,20 @@ public: | |||
13 | 13 | ||
14 | void killScript(id_type entity); | 14 | void killScript(id_type entity); |
15 | 15 | ||
16 | id_type runBehaviorScript(id_type entity); | 16 | void startBehavior(id_type entity); |
17 | |||
18 | void stopBehavior(id_type entity); | ||
17 | 19 | ||
18 | void onTouch(id_type entity, id_type player); | 20 | void onTouch(id_type entity, id_type player); |
19 | 21 | ||
20 | private: | 22 | private: |
21 | 23 | ||
22 | template <typename... Args> | 24 | template <typename... Args> |
23 | id_type runScript(std::string event, id_type entity, Args&&... args); | 25 | sol::optional<id_type> runScript( |
26 | std::string table, | ||
27 | std::string event, | ||
28 | id_type entity, | ||
29 | Args&&... args); | ||
24 | 30 | ||
25 | sol::state engine_; | 31 | sol::state engine_; |
26 | }; | 32 | }; |