MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
Expanse.hpp
Go to the documentation of this file.
1#pragma once
2
6
7namespace MayaFlux::Nexus {
8
9/**
10 * @struct CrossingContext
11 * @brief Data passed to a typed Expanse action for one entity.
12 *
13 * Entity ids are per Fabric, and one Expanse may be registered on several, so
14 * an entity is only identified by @c fabric_id together with @c entity.
15 */
17 uint32_t fabric_id {}; ///< Id of the Fabric that evaluated the Expanse.
18 uint32_t entity {}; ///< Id of the entity within that Fabric.
19 std::optional<glm::vec3> position; ///< Position at this commit. Empty for an exit when the entity has left the Fabric.
20};
21
22/**
23 * @class Expanse
24 * @brief A defined region of space with a containment test and crossing actions.
25 *
26 * An Expanse is an extent, not a point. It answers whether a world position
27 * lies within it, and fires actions when an entity crosses its edge: one on
28 * entry, one on exit, and optionally one on every commit while inside. The
29 * Fabric evaluates the Expanse against every indexed position on each commit
30 * and diffs membership against the previous commit. The Expanse does not
31 * query; the Fabric drives it, because only the Fabric sees consecutive
32 * snapshots.
33 *
34 * The region is a box, a predicate, or both. A box is plain data: it moves or
35 * resizes with set_bounds, which any influence can drive through bind_bounds.
36 * A predicate is a function from position to bool that closes over whatever
37 * state shapes the region. With both, the box is a cheap first test and both
38 * must pass. Derived classes may replace contains().
39 *
40 * Crossing actions are either a plain function of the entity id, or any number
41 * of typed actions added with add_entry, add_exit and add_inside. A typed
42 * action builds a value from the CrossingContext and hands it to its targets,
43 * so the targets in FieldTargets.hpp work here as they do for an Agent. What
44 * crossing means computationally is the action's decision.
45 *
46 * An Expanse also perceives its own contents, as a Sensor perceives its
47 * neighbourhood. add_perception sources receive a PerceptionContext centred on
48 * the region, with the occupants as the spatial results, once per evaluate and
49 * before any crossing action. Typed actions and perceptions are runtime objects
50 * and are not part of encoded state.
51 *
52 * The id is assigned at construction and is the same on every Fabric the
53 * Expanse is registered on.
54 */
55class MAYAFLUX_API Expanse {
56public:
57 using ContainsFn = std::function<bool(const glm::vec3&)>;
58 using CrossingFn = std::function<void(uint32_t)>;
59
60 /**
61 * @brief Construct with a containment predicate and crossing actions.
62 * @param contains Returns true when a world position lies within the Expanse.
63 * @param on_enter Fired with the entity id when it enters. May be empty.
64 * @param on_exit Fired with the entity id when it leaves. May be empty.
65 */
67 : m_contains(std::move(contains))
68 , m_on_enter(std::move(on_enter))
69 , m_on_exit(std::move(on_exit))
70 {
71 }
72
73 /**
74 * @brief Construct with a containment predicate only, for an Expanse whose
75 * behaviour comes from typed actions.
76 * @param contains Returns true when a world position lies within the Expanse.
77 */
79 : m_contains(std::move(contains))
80 {
81 }
82
83 /**
84 * @brief Construct as a box.
85 * @param bounds World-space box.
86 * @param on_enter Fired with the entity id when it enters. May be empty.
87 * @param on_exit Fired with the entity id when it leaves. May be empty.
88 */
89 explicit Expanse(const Kinesis::AABB3D& bounds, CrossingFn on_enter = {}, CrossingFn on_exit = {})
90 : m_on_enter(std::move(on_enter))
91 , m_on_exit(std::move(on_exit))
92 , m_bounds(bounds)
93 {
94 }
95
96 /**
97 * @brief Construct with a named containment predicate and crossing actions.
98 * @param fn_name Identifier used for state encoding.
99 * @param contains Returns true when a world position lies within the Expanse.
100 * @param on_enter Fired with the entity id when it enters. May be empty.
101 * @param on_exit Fired with the entity id when it leaves. May be empty.
102 */
104 : m_fn_name(std::move(fn_name))
105 , m_contains(std::move(contains))
106 , m_on_enter(std::move(on_enter))
107 , m_on_exit(std::move(on_exit))
108 {
109 }
110
111 /**
112 * @brief Construct with named containment predicate and named crossing actions.
113 * @param fn_name Identifier for the containment predicate.
114 * @param on_enter_fn_name Identifier for the on_enter callable.
115 * @param on_exit_fn_name Identifier for the on_exit callable.
116 * @param contains Returns true when a world position lies within the Expanse.
117 * @param on_enter Fired with the entity id when it enters. May be empty.
118 * @param on_exit Fired with the entity id when it leaves. May be empty.
119 */
120 Expanse(std::string fn_name,
121 std::string on_enter_fn_name,
122 std::string on_exit_fn_name,
126 : m_fn_name(std::move(fn_name))
127 , m_on_enter_fn_name(std::move(on_enter_fn_name))
128 , m_on_exit_fn_name(std::move(on_exit_fn_name))
129 , m_contains(std::move(contains))
130 , m_on_enter(std::move(on_enter))
131 , m_on_exit(std::move(on_exit))
132 {
133 }
134
135 virtual ~Expanse() = default;
136
137 /** @brief Test whether a world position lies within the Expanse. */
138 [[nodiscard]] virtual bool contains(const glm::vec3& p) const
139 {
140 if (m_bounds && !m_bounds->contains(p)) {
141 return false;
142 }
143 return m_contains ? m_contains(p) : m_bounds.has_value();
144 }
145
146 /** @brief Set or replace the box. */
147 virtual void set_bounds(const Kinesis::AABB3D& bounds) { m_bounds = bounds; }
148
149 /** @brief Remove the box, leaving the predicate. */
150 void clear_bounds() { m_bounds.reset(); }
151
152 /** @brief The box, if set. */
153 [[nodiscard]] const std::optional<Kinesis::AABB3D>& bounds() const { return m_bounds; }
154
155 /** @brief Identifier assigned to the containment predicate, empty if anonymous. */
156 [[nodiscard]] const std::string& fn_name() const { return m_fn_name; }
157
158 /** @brief Set or replace the predicate identifier. */
159 void set_fn_name(std::string name) { m_fn_name = std::move(name); }
160
161 /** @brief Identifier assigned to the on_enter callable, empty if anonymous. */
162 [[nodiscard]] const std::string& on_enter_fn_name() const { return m_on_enter_fn_name; }
163
164 /** @brief Set or replace the on_enter callable identifier. */
165 void set_on_enter_fn_name(std::string name) { m_on_enter_fn_name = std::move(name); }
166
167 /** @brief Identifier assigned to the on_exit callable, empty if anonymous. */
168 [[nodiscard]] const std::string& on_exit_fn_name() const { return m_on_exit_fn_name; }
169
170 /** @brief Set or replace the on_exit callable identifier. */
171 void set_on_exit_fn_name(std::string name) { m_on_exit_fn_name = std::move(name); }
172
173 /** @brief Id assigned at construction. Unique across Expanses and stable for its lifetime. */
174 [[nodiscard]] uint32_t id() const { return m_id; }
175
176 /** @brief Entity ids inside this Expanse for @p fabric_id, or nullptr if that fabric has no occupants. */
177 [[nodiscard]] const std::unordered_set<uint32_t>* occupants(uint32_t fabric_id) const
178 {
179 auto it = m_occupants_by_fabric.find(fabric_id);
180 return it != m_occupants_by_fabric.end() ? &it->second : nullptr;
181 }
182
183 /** @brief All fabric ids that currently have at least one occupant inside this Expanse. */
184 [[nodiscard]] std::vector<uint32_t> occupied_fabrics() const
185 {
186 std::vector<uint32_t> result;
187 result.reserve(m_occupants_by_fabric.size());
188 for (const auto& [fid, _] : m_occupants_by_fabric)
189 result.push_back(fid);
190 return result;
191 }
192
193 /**
194 * @brief Add an action of any type that runs when an entity enters.
195 * @tparam T Type the producer returns; whatever the targets take.
196 * @param producer Builds the value from the crossing context.
197 * @return The influence, to add its targets with Influence::add_target.
198 */
199 template <typename T>
200 std::shared_ptr<Influence<T, CrossingContext>> add_entry(
202 {
203 return add_action<T>(m_entries, std::move(producer));
204 }
205
206 /**
207 * @brief Add an action of any type that runs when an entity leaves.
208 * @tparam T Type the producer returns; whatever the targets take.
209 * @param producer Builds the value from the crossing context.
210 * @return The influence, to add its targets with Influence::add_target.
211 */
212 template <typename T>
213 std::shared_ptr<Influence<T, CrossingContext>> add_exit(
215 {
216 return add_action<T>(m_exits, std::move(producer));
217 }
218
219 /**
220 * @brief Add an action of any type that runs on every commit for each entity inside.
221 * @tparam T Type the producer returns; whatever the targets take.
222 * @param producer Builds the value from the crossing context.
223 * @return The influence, to add its targets with Influence::add_target.
224 */
225 template <typename T>
226 std::shared_ptr<Influence<T, CrossingContext>> add_inside(
228 {
229 return add_action<T>(m_insides, std::move(producer));
230 }
231
232 /** @brief Stop an action added with add_entry(), add_exit() or add_inside(). */
233 template <typename T>
234 void remove_crossing(const std::shared_ptr<Influence<T, CrossingContext>>& action)
235 {
236 for (auto* list : { &m_entries, &m_exits, &m_insides }) {
237 std::erase_if(*list, [&action](const auto& entry) { return entry.first == action; });
238 }
239 }
240
241 /** @brief Stop every typed action. */
243 {
244 m_entries.clear();
245 m_exits.clear();
246 m_insides.clear();
247 }
248
249 /**
250 * @brief Add a perception of any type over the entities inside, with as many targets as are wanted.
251 * @tparam T Type the source returns; whatever the targets take.
252 * @param source Reads the value from a PerceptionContext whose position and radius
253 * are the region's centre and reach, and whose spatial results are
254 * the occupants: entity id and squared distance from the centre.
255 * @return The perception, to add its targets with Perception::add_target.
256 *
257 * Runs once per evaluate for each Fabric, before the crossing actions.
258 */
259 template <typename T>
260 std::shared_ptr<Perception<T>> add_perception(typename Perception<T>::Source source)
261 {
262 auto perception = std::make_shared<Perception<T>>(std::move(source));
263 m_perceptions.emplace_back(perception,
264 [perception](const PerceptionContext& ctx) { perception->invoke(ctx); });
265 return perception;
266 }
267
268 /** @brief Stop a perception added with add_perception(). */
269 template <typename T>
270 void remove_perception(const std::shared_ptr<Perception<T>>& perception)
271 {
272 std::erase_if(m_perceptions,
273 [&perception](const auto& entry) { return entry.first == perception; });
274 }
275
276 /** @brief Stop every perception added with add_perception(). */
277 void clear_perceptions() { m_perceptions.clear(); }
278
279 /**
280 * @brief Evaluate a spatial snapshot from one Fabric against this Expanse.
281 *
282 * Tests each position in @p snapshot with contains(), diffs the result
283 * against the previous occupant set for @p fabric_id, fires the entry and
284 * exit actions for the difference and the inside actions for everything
285 * inside, and updates the stored set. Each Fabric maintains independent
286 * occupant state.
287 *
288 * @param fabric_id Stable id of the calling Fabric.
289 * @param snapshot Entity id and position of every positioned entity in that Fabric.
290 */
291 void evaluate(uint32_t fabric_id,
292 std::span<const std::pair<uint32_t, glm::vec3>> snapshot);
293
294 /**
295 * @brief Fire the exit actions for everything inside for @p fabric_id and forget it.
296 *
297 * Called by a Fabric when it drops this Expanse, so state an entry action
298 * changed is not left in the entered state.
299 *
300 * @param fabric_id Stable id of the Fabric dropping the Expanse.
301 */
302 void evict(uint32_t fabric_id);
303
304protected:
305 /** @brief Called at the start of every evaluate(), before any position is tested. */
306 virtual void begin_evaluate() { }
307
308 /**
309 * @brief Where the region is centred and how far it reaches, for perceptions.
310 *
311 * The centre and the half diagonal of the box if there is one, else the
312 * origin and zero. A derived Expanse whose region is not the box overrides it.
313 */
314 [[nodiscard]] virtual std::pair<glm::vec3, float> reach() const
315 {
316 if (m_bounds) {
317 return { m_bounds->center(), glm::length(m_bounds->half_extent()) };
318 }
319 return { glm::vec3(0.0F), 0.0F };
320 }
321
322private:
323 using Action = std::function<void(const CrossingContext&)>;
324 using Actions = std::vector<std::pair<std::shared_ptr<void>, Action>>;
325 using PerceptionFn = std::function<void(const PerceptionContext&)>;
326
327 template <typename T>
328 static std::shared_ptr<Influence<T, CrossingContext>> add_action(
329 Actions& list,
331 {
332 auto action = std::make_shared<Influence<T, CrossingContext>>(std::move(producer));
333 list.emplace_back(action,
334 [action](const CrossingContext& ctx) { action->invoke(ctx); });
335 return action;
336 }
337
338 std::string m_fn_name;
340 std::string m_on_exit_fn_name;
344 std::optional<Kinesis::AABB3D> m_bounds;
345
349 std::vector<std::pair<std::shared_ptr<void>, PerceptionFn>> m_perceptions;
350
351 static uint32_t allocate_id();
352
353 uint32_t m_id { allocate_id() };
354 std::unordered_map<uint32_t, std::unordered_set<uint32_t>> m_occupants_by_fabric;
355};
356
357} // namespace MayaFlux::Nexus
Expanse::ContainsFn contains
Definition Decoder.cpp:517
Expanse::CrossingFn on_enter
Definition Decoder.cpp:518
Expanse::CrossingFn on_exit
Definition Decoder.cpp:519
std::string name
Definition VKDevice.cpp:123
virtual bool contains(const glm::vec3 &p) const
Test whether a world position lies within the Expanse.
Definition Expanse.hpp:138
void set_on_exit_fn_name(std::string name)
Set or replace the on_exit callable identifier.
Definition Expanse.hpp:171
std::shared_ptr< Influence< T, CrossingContext > > add_inside(typename Influence< T, CrossingContext >::Producer producer)
Add an action of any type that runs on every commit for each entity inside.
Definition Expanse.hpp:226
std::vector< std::pair< std::shared_ptr< void >, Action > > Actions
Definition Expanse.hpp:324
const std::string & on_enter_fn_name() const
Identifier assigned to the on_enter callable, empty if anonymous.
Definition Expanse.hpp:162
const std::unordered_set< uint32_t > * occupants(uint32_t fabric_id) const
Entity ids inside this Expanse for fabric_id, or nullptr if that fabric has no occupants.
Definition Expanse.hpp:177
std::string m_on_enter_fn_name
Definition Expanse.hpp:339
std::unordered_map< uint32_t, std::unordered_set< uint32_t > > m_occupants_by_fabric
Definition Expanse.hpp:354
std::function< void(uint32_t)> CrossingFn
Definition Expanse.hpp:58
std::shared_ptr< Influence< T, CrossingContext > > add_entry(typename Influence< T, CrossingContext >::Producer producer)
Add an action of any type that runs when an entity enters.
Definition Expanse.hpp:200
virtual ~Expanse()=default
std::optional< Kinesis::AABB3D > m_bounds
Definition Expanse.hpp:344
std::vector< uint32_t > occupied_fabrics() const
All fabric ids that currently have at least one occupant inside this Expanse.
Definition Expanse.hpp:184
void set_fn_name(std::string name)
Set or replace the predicate identifier.
Definition Expanse.hpp:159
std::shared_ptr< Influence< T, CrossingContext > > add_exit(typename Influence< T, CrossingContext >::Producer producer)
Add an action of any type that runs when an entity leaves.
Definition Expanse.hpp:213
const std::string & on_exit_fn_name() const
Identifier assigned to the on_exit callable, empty if anonymous.
Definition Expanse.hpp:168
std::shared_ptr< Perception< T > > add_perception(typename Perception< T >::Source source)
Add a perception of any type over the entities inside, with as many targets as are wanted.
Definition Expanse.hpp:260
std::function< void(const CrossingContext &)> Action
Definition Expanse.hpp:323
std::string m_on_exit_fn_name
Definition Expanse.hpp:340
const std::optional< Kinesis::AABB3D > & bounds() const
The box, if set.
Definition Expanse.hpp:153
std::vector< std::pair< std::shared_ptr< void >, PerceptionFn > > m_perceptions
Definition Expanse.hpp:349
virtual void set_bounds(const Kinesis::AABB3D &bounds)
Set or replace the box.
Definition Expanse.hpp:147
void clear_crossings()
Stop every typed action.
Definition Expanse.hpp:242
std::function< bool(const glm::vec3 &)> ContainsFn
Definition Expanse.hpp:57
void clear_perceptions()
Stop every perception added with add_perception().
Definition Expanse.hpp:277
uint32_t id() const
Id assigned at construction.
Definition Expanse.hpp:174
const std::string & fn_name() const
Identifier assigned to the containment predicate, empty if anonymous.
Definition Expanse.hpp:156
Expanse(const Kinesis::AABB3D &bounds, CrossingFn on_enter={}, CrossingFn on_exit={})
Construct as a box.
Definition Expanse.hpp:89
void remove_crossing(const std::shared_ptr< Influence< T, CrossingContext > > &action)
Stop an action added with add_entry(), add_exit() or add_inside().
Definition Expanse.hpp:234
Expanse(std::string fn_name, std::string on_enter_fn_name, std::string on_exit_fn_name, ContainsFn contains, CrossingFn on_enter, CrossingFn on_exit)
Construct with named containment predicate and named crossing actions.
Definition Expanse.hpp:120
void set_on_enter_fn_name(std::string name)
Set or replace the on_enter callable identifier.
Definition Expanse.hpp:165
Expanse(ContainsFn contains)
Construct with a containment predicate only, for an Expanse whose behaviour comes from typed actions.
Definition Expanse.hpp:78
std::function< void(const PerceptionContext &)> PerceptionFn
Definition Expanse.hpp:325
virtual std::pair< glm::vec3, float > reach() const
Where the region is centred and how far it reaches, for perceptions.
Definition Expanse.hpp:314
static std::shared_ptr< Influence< T, CrossingContext > > add_action(Actions &list, typename Influence< T, CrossingContext >::Producer producer)
Definition Expanse.hpp:328
void clear_bounds()
Remove the box, leaving the predicate.
Definition Expanse.hpp:150
Expanse(ContainsFn contains, CrossingFn on_enter, CrossingFn on_exit)
Construct with a containment predicate and crossing actions.
Definition Expanse.hpp:66
virtual void begin_evaluate()
Called at the start of every evaluate(), before any position is tested.
Definition Expanse.hpp:306
Expanse(std::string fn_name, ContainsFn contains, CrossingFn on_enter, CrossingFn on_exit)
Construct with a named containment predicate and crossing actions.
Definition Expanse.hpp:103
void remove_perception(const std::shared_ptr< Perception< T > > &perception)
Stop a perception added with add_perception().
Definition Expanse.hpp:270
A defined region of space with a containment test and crossing actions.
Definition Expanse.hpp:55
std::function< T(const Context &)> Producer
Definition Influence.hpp:93
A value of any type, built from the influence context and handed to its targets.
Definition Influence.hpp:91
std::function< T(const PerceptionContext &)> Source
A value of any type, read from the world through the perception context and handed to its targets.
Axis-aligned bounding box in 3D world space.
Definition Bounds.hpp:188
std::optional< glm::vec3 > position
Position at this commit. Empty for an exit when the entity has left the Fabric.
Definition Expanse.hpp:19
uint32_t fabric_id
Id of the Fabric that evaluated the Expanse.
Definition Expanse.hpp:17
uint32_t entity
Id of the entity within that Fabric.
Definition Expanse.hpp:18
Data passed to a typed Expanse action for one entity.
Definition Expanse.hpp:16
Data passed to a Sensor or Agent perception function on each commit.