libultraship
Reimplementations of libultra (N64 SDK) functions for modern hardware
Loading...
Searching...
No Matches
Component.h
Go to the documentation of this file.
1#pragma once
2
3#pragma once
4
5#include <string>
6#include <memory>
7#include <vector>
8#include <algorithm>
9#ifdef COMPONENT_THREAD_SAFE
10#include <atomic>
11#include <mutex>
12#endif
13#include <queue>
14#include <unordered_set>
15#include <nlohmann/json.hpp>
16#include <stdexcept>
17#include <type_traits>
18
19#include "ship/core/Part.h"
20#include "ship/core/PartList.h"
22
23namespace Ship {
24
34class Component : public Part, public std::enable_shared_from_this<Component> {
35 public:
40 explicit Component(const std::string& name, std::shared_ptr<Context> context = nullptr);
41 virtual ~Component();
42
60 void Init(const nlohmann::json& initArgs = nlohmann::json::object());
61
71 bool IsInitialized() const;
72
74 const std::string& GetName() const;
75
77 std::string ToString() const;
78
83 std::string ToTreeString(int depth = 0) const;
84
86 explicit operator std::string() const;
87
88 // ---- Parent/child relationship accessors ----
89
94
98 const ComponentList& GetChildren() const;
99
106 virtual std::shared_ptr<Component> TryGetSharedComponent() noexcept;
107
115 virtual std::shared_ptr<Component> GetSharedComponent();
116
117 // ---- Breadth-first hierarchy search ----
118
124 template <typename T> bool HasInChildren() const;
125
131 template <typename T> std::shared_ptr<T> GetFirstInChildren() const;
132
138 template <typename T> std::shared_ptr<std::vector<std::shared_ptr<T>>> GetInChildren() const;
139
140 // ---- Breadth-first ancestor search ----
141
147 template <typename T> bool HasInParents() const;
148
154 template <typename T> std::shared_ptr<T> GetFirstInParents() const;
155
161 template <typename T> std::shared_ptr<std::vector<std::shared_ptr<T>>> GetInParents() const;
162
163 protected:
179 virtual void OnInit(const nlohmann::json& initArgs = nlohmann::json::object());
180
190
200 template <typename T>
201 std::shared_ptr<T> RequireDependency(const std::shared_ptr<T>& dependency, const std::string& dependencyName) const;
202
203 private:
210 std::string ToTreeStringImpl(int depth, std::unordered_set<uint64_t>& visited) const;
211
212 std::string mName;
213 std::weak_ptr<Component> mWeakSelf;
214#ifdef COMPONENT_THREAD_SAFE
215 std::atomic<bool> mIsInitialized{ false };
216 mutable std::recursive_mutex mInitMutex;
217#else
218 bool mIsInitialized = false;
219#endif
220 ParentComponentList mParents;
221 ComponentList mChildren;
222};
223
224// ---- Template BFS implementations (children) ----
225// All three methods perform a full breadth-first traversal through the entire
226// descendant tree. Every node's children are enqueued for further exploration,
227// so the search is not limited to any particular depth.
228
229template <typename T> bool Component::HasInChildren() const {
230 std::queue<std::shared_ptr<const Component>> queue;
231 std::unordered_set<uint64_t> visited;
232 visited.insert(GetId());
233
234 // Seed the queue with this component's direct children. The root itself is
235 // handled here (rather than enqueued) so we never require it to be owned by
236 // a shared_ptr.
237 auto seed = GetChildren().Get();
238 for (const auto& child : *seed) {
239 if (visited.insert(child->GetId()).second) {
240 if (std::dynamic_pointer_cast<T>(child)) {
241 return true;
242 }
243 queue.push(child);
244 }
245 }
246
247 // BFS: process every node, enqueue its children so we reach all depths.
248 // The queue owns a shared_ptr to each node, so nodes stay alive even if a
249 // concurrent Remove drops the last owning reference held by a parent list.
250 while (!queue.empty()) {
251 auto current = queue.front();
252 queue.pop();
253
254 auto currentChildren = current->GetChildren().Get();
255 for (const auto& child : *currentChildren) {
256 if (visited.insert(child->GetId()).second) {
257 if (std::dynamic_pointer_cast<T>(child)) {
258 return true;
259 }
260 queue.push(child);
261 }
262 }
263 }
264 return false;
265}
266
267template <typename T>
268std::shared_ptr<T> Component::RequireDependency(const std::shared_ptr<T>& dependency,
269 const std::string& dependencyName) const {
270 static_assert(std::is_base_of_v<Component, T>, "RequireDependency only supports Component dependencies");
271
272 if (!dependency) {
273 throw std::runtime_error("Component '" + GetName() + "' requires dependency '" + dependencyName +
274 "' to exist before use");
275 }
276
277 if (!dependency->IsInitialized()) {
278 throw std::runtime_error("Component '" + GetName() + "' requires dependency '" + dependencyName +
279 "' to be initialized before use");
280 }
281
282 return dependency;
283}
284
285template <typename T> std::shared_ptr<T> Component::GetFirstInChildren() const {
286 std::queue<std::shared_ptr<const Component>> queue;
287 std::unordered_set<uint64_t> visited;
288 visited.insert(GetId());
289
290 auto seed = GetChildren().Get();
291 for (const auto& child : *seed) {
292 if (visited.insert(child->GetId()).second) {
293 auto typed = std::dynamic_pointer_cast<T>(child);
294 if (typed) {
295 return typed;
296 }
297 queue.push(child);
298 }
299 }
300
301 while (!queue.empty()) {
302 auto current = queue.front();
303 queue.pop();
304
305 auto currentChildren = current->GetChildren().Get();
306 for (const auto& child : *currentChildren) {
307 if (visited.insert(child->GetId()).second) {
308 auto typed = std::dynamic_pointer_cast<T>(child);
309 if (typed) {
310 return typed;
311 }
312 queue.push(child);
313 }
314 }
315 }
316 return nullptr;
317}
318
319template <typename T> std::shared_ptr<std::vector<std::shared_ptr<T>>> Component::GetInChildren() const {
320 auto result = std::make_shared<std::vector<std::shared_ptr<T>>>();
321 std::queue<std::shared_ptr<const Component>> queue;
322 std::unordered_set<uint64_t> visited;
323 visited.insert(GetId());
324
325 auto seed = GetChildren().Get();
326 for (const auto& child : *seed) {
327 if (visited.insert(child->GetId()).second) {
328 auto typed = std::dynamic_pointer_cast<T>(child);
329 if (typed) {
330 result->push_back(typed);
331 }
332 queue.push(child);
333 }
334 }
335
336 while (!queue.empty()) {
337 auto current = queue.front();
338 queue.pop();
339
340 auto currentChildren = current->GetChildren().Get();
341 for (const auto& child : *currentChildren) {
342 if (visited.insert(child->GetId()).second) {
343 auto typed = std::dynamic_pointer_cast<T>(child);
344 if (typed) {
345 result->push_back(typed);
346 }
347 queue.push(child);
348 }
349 }
350 }
351 return result;
352}
353
354// ---- Template BFS implementations (parents) ----
355// Mirror of the InChildren methods, but traversing upward through parents.
356
357template <typename T> bool Component::HasInParents() const {
358 std::queue<std::shared_ptr<const Component>> queue;
359 std::unordered_set<uint64_t> visited;
360 visited.insert(GetId());
361
362 auto seed = GetParents().Get();
363 for (const auto& parent : *seed) {
364 if (visited.insert(parent->GetId()).second) {
365 if (std::dynamic_pointer_cast<T>(parent)) {
366 return true;
367 }
368 queue.push(parent);
369 }
370 }
371
372 while (!queue.empty()) {
373 auto current = queue.front();
374 queue.pop();
375
376 auto currentParents = current->GetParents().Get();
377 for (const auto& parent : *currentParents) {
378 if (visited.insert(parent->GetId()).second) {
379 if (std::dynamic_pointer_cast<T>(parent)) {
380 return true;
381 }
382 queue.push(parent);
383 }
384 }
385 }
386 return false;
387}
388
389template <typename T> std::shared_ptr<T> Component::GetFirstInParents() const {
390 std::queue<std::shared_ptr<const Component>> queue;
391 std::unordered_set<uint64_t> visited;
392 visited.insert(GetId());
393
394 auto seed = GetParents().Get();
395 for (const auto& parent : *seed) {
396 if (visited.insert(parent->GetId()).second) {
397 auto typed = std::dynamic_pointer_cast<T>(parent);
398 if (typed) {
399 return typed;
400 }
401 queue.push(parent);
402 }
403 }
404
405 while (!queue.empty()) {
406 auto current = queue.front();
407 queue.pop();
408
409 auto currentParents = current->GetParents().Get();
410 for (const auto& parent : *currentParents) {
411 if (visited.insert(parent->GetId()).second) {
412 auto typed = std::dynamic_pointer_cast<T>(parent);
413 if (typed) {
414 return typed;
415 }
416 queue.push(parent);
417 }
418 }
419 }
420 return nullptr;
421}
422
423template <typename T> std::shared_ptr<std::vector<std::shared_ptr<T>>> Component::GetInParents() const {
424 auto result = std::make_shared<std::vector<std::shared_ptr<T>>>();
425 std::queue<std::shared_ptr<const Component>> queue;
426 std::unordered_set<uint64_t> visited;
427 visited.insert(GetId());
428
429 auto seed = GetParents().Get();
430 for (const auto& parent : *seed) {
431 if (visited.insert(parent->GetId()).second) {
432 auto typed = std::dynamic_pointer_cast<T>(parent);
433 if (typed) {
434 result->push_back(typed);
435 }
436 queue.push(parent);
437 }
438 }
439
440 while (!queue.empty()) {
441 auto current = queue.front();
442 queue.pop();
443
444 auto currentParents = current->GetParents().Get();
445 for (const auto& parent : *currentParents) {
446 if (visited.insert(parent->GetId()).second) {
447 auto typed = std::dynamic_pointer_cast<T>(parent);
448 if (typed) {
449 result->push_back(typed);
450 }
451 queue.push(parent);
452 }
453 }
454 }
455 return result;
456}
457
458// ---- ComponentList template implementations ----
459// These live here because they need the full Component definition,
460// while ComponentList.h only forward-declares Component to break the
461// circular dependency.
462
463template <typename StoredPtr>
464template <typename T>
465bool BasicComponentList<StoredPtr>::Has(const std::string& name) const {
466#ifdef COMPONENT_THREAD_SAFE
467 const std::lock_guard<std::recursive_mutex> lock(this->GetMutex());
468#endif
469 auto list = this->Get();
470 return std::find_if(list->begin(), list->end(), [&name](const std::shared_ptr<Component>& c) {
471 return c->GetName() == name && std::dynamic_pointer_cast<T>(c) != nullptr;
472 }) != list->end();
473}
474
475template <typename StoredPtr>
476template <typename T>
477std::shared_ptr<std::vector<std::shared_ptr<T>>> BasicComponentList<StoredPtr>::Get(const std::string& name) const {
478#ifdef COMPONENT_THREAD_SAFE
479 const std::lock_guard<std::recursive_mutex> lock(this->GetMutex());
480#endif
481 auto result = std::make_shared<std::vector<std::shared_ptr<T>>>();
482 auto list = this->Get();
483 for (const auto& c : *list) {
484 auto typed = std::dynamic_pointer_cast<T>(c);
485 if (typed && c->GetName() == name) {
486 result->push_back(typed);
487 }
488 }
489 return result;
490}
491
492} // namespace Ship
std::shared_ptr< std::vector< std::shared_ptr< Component > > > Get(const std::string &name) const
Returns all components with the given name.
A named Part with a parent/child hierarchy and optional thread safety.
Definition Component.h:34
Component(const std::string &name, std::shared_ptr< Context > context=nullptr)
Constructs a Component with the given name.
bool IsInitialized() const
Returns true once Init() (or MarkInitialized()) has completed successfully.
virtual void OnInit(const nlohmann::json &initArgs=nlohmann::json::object())
Override this to implement component-specific initialization logic.
std::shared_ptr< T > GetFirstInChildren() const
Returns the first descendant that matches type T via BFS.
Definition Component.h:285
void Init(const nlohmann::json &initArgs=nlohmann::json::object())
Performs one-time initialization of this component.
bool HasInParents() const
Checks whether any ancestor Component matches type T via BFS.
Definition Component.h:357
std::shared_ptr< std::vector< std::shared_ptr< T > > > GetInChildren() const
Returns all descendants that match type T via BFS.
Definition Component.h:319
const ComponentList & GetChildren() const
Returns a const reference to the child list.
ParentComponentList & GetParents()
Returns a mutable reference to the parent list.
virtual std::shared_ptr< Component > GetSharedComponent()
Returns a shared_ptr to this Component via the correct enable_shared_from_this base.
const std::string & GetName() const
Returns the name of this Component.
virtual ~Component()
void MarkInitialized()
Marks this component as initialized without going through Init().
std::shared_ptr< T > GetFirstInParents() const
Returns the first ancestor that matches type T via BFS.
Definition Component.h:389
std::shared_ptr< std::vector< std::shared_ptr< T > > > GetInParents() const
Returns all ancestors that match type T via BFS.
Definition Component.h:423
std::shared_ptr< T > RequireDependency(const std::shared_ptr< T > &dependency, const std::string &dependencyName) const
Returns a cached dependency after validating it exists and is initialized.
Definition Component.h:268
ComponentList & GetChildren()
Returns a mutable reference to the child list.
std::string ToString() const
Returns a human-readable string representation (e.g. "Name (id)").
std::string ToTreeString(int depth=0) const
Returns a human-readable tree representation of this component and its children.
const ParentComponentList & GetParents() const
Returns a const reference to the parent list.
virtual std::shared_ptr< Component > TryGetSharedComponent() noexcept
Returns a shared_ptr to this Component when available, otherwise nullptr.
bool HasInChildren() const
Checks whether any descendant Component matches type T via BFS.
Definition Component.h:229
bool Has() const
Checks whether any Part of type T is in the list.
Definition PartList.h:777
std::shared_ptr< std::vector< std::shared_ptr< Component > > > Get() const
Returns a snapshot (copy) of all currently live Parts in the list.
Definition PartList.h:825
Base class for all identifiable objects in the component system.
Definition Part.h:27
uint64_t GetId() const
Returns the unique identifier for this Part.
Core namespace for the libultraship engine framework.
Definition gfx_direct3d_common.h:14