Line data Source code
1 : /*
2 : * Copyright (C) 2025 aeml
3 : *
4 : * This program is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 3 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * This program is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 : */
17 :
18 : #include "simlab/Scenario.hpp"
19 :
20 : #include <algorithm>
21 : #include <atomic>
22 : #include <mutex>
23 :
24 : namespace simlab
25 : {
26 : namespace
27 : {
28 : // Ensure thread-safe registration in case of static init across TUs
29 648 : std::mutex& regMutex()
30 : {
31 : static std::mutex m;
32 648 : return m;
33 : }
34 : }
35 :
36 : namespace
37 : {
38 769 : std::atomic<bool>& HeadlessFlag()
39 : {
40 : static std::atomic<bool> flag{false};
41 769 : return flag;
42 : }
43 :
44 81 : void RegisterBuiltIns()
45 : {
46 : // Register new showcase scenarios
47 81 : ScenarioRegistry::Register("gravity", "Planetary Gravity (N-Body)", &CreatePlanetaryGravityScenario);
48 81 : ScenarioRegistry::Register("wrecking", "Wrecking Ball (Joints & Collisions)", &CreateWreckingBallScenario);
49 81 : ScenarioRegistry::Register("fluid", "Particle Fluid (High Entity Count)", &CreateParticleFluidScenario);
50 81 : ScenarioRegistry::Register("demo", "Full Demo (All Systems Active)", &CreateFullDemoScenario);
51 81 : ScenarioRegistry::Register("fail_setup", "Fail Setup (Test Scenario)", &CreateFailSetupScenario);
52 81 : ScenarioRegistry::Register("fail_update", "Fail Update (Test Scenario)", &CreateFailUpdateScenario);
53 81 : ScenarioRegistry::Register("fail_world_update", "Fail World Update (Test Scenario)", &CreateFailWorldUpdateScenario);
54 81 : ScenarioRegistry::Register("fail_render", "Fail Render (Test Scenario)", &CreateFailRenderScenario);
55 81 : }
56 : }
57 :
58 721 : std::vector<ScenarioDesc>& ScenarioRegistry::Storage()
59 : {
60 721 : static std::vector<ScenarioDesc> s_storage;
61 721 : return s_storage;
62 : }
63 :
64 648 : void ScenarioRegistry::Register(const char* key, const char* title, ScenarioFactory factory)
65 : {
66 648 : if (!key || !title || !factory)
67 : {
68 0 : return;
69 : }
70 648 : std::lock_guard<std::mutex> lock{regMutex()};
71 648 : auto& st = Storage();
72 8892 : auto it = std::find_if(st.begin(), st.end(), [&](const ScenarioDesc& d){ return std::string(d.key) == key; });
73 648 : if (it == st.end())
74 : {
75 168 : st.push_back(ScenarioDesc{key, title, factory, nullptr, nullptr});
76 : }
77 648 : }
78 :
79 0 : void ScenarioRegistry::Register(const char* key, const char* title, ScenarioFactory factory,
80 : const char* category, const char* subcategory)
81 : {
82 0 : if (!key || !title || !factory)
83 : {
84 0 : return;
85 : }
86 0 : std::lock_guard<std::mutex> lock{regMutex()};
87 0 : auto& st = Storage();
88 0 : auto it = std::find_if(st.begin(), st.end(), [&](const ScenarioDesc& d){ return std::string(d.key) == key; });
89 0 : if (it == st.end())
90 : {
91 0 : st.push_back(ScenarioDesc{key, title, factory, category, subcategory});
92 : }
93 : else
94 : {
95 0 : it->category = category;
96 0 : it->subcategory = subcategory;
97 : }
98 0 : }
99 :
100 44 : const std::vector<ScenarioDesc>& ScenarioRegistry::All()
101 : {
102 : static std::once_flag once;
103 44 : std::call_once(once, RegisterBuiltIns);
104 44 : return Storage();
105 : }
106 :
107 29 : ScenarioFactory ScenarioRegistry::FindFactory(const std::string& key)
108 : {
109 : static std::once_flag once;
110 29 : std::call_once(once, RegisterBuiltIns);
111 29 : auto& st = Storage();
112 131 : auto it = std::find_if(st.begin(), st.end(), [&](const ScenarioDesc& d){ return key == d.key; });
113 29 : if (it != st.end())
114 : {
115 26 : return it->factory;
116 : }
117 3 : return nullptr;
118 : }
119 :
120 28 : std::unique_ptr<IScenario> ScenarioRegistry::Create(const std::string& key)
121 : {
122 : static std::once_flag once;
123 28 : std::call_once(once, RegisterBuiltIns);
124 28 : auto f = FindFactory(key);
125 28 : if (f)
126 : {
127 25 : return f();
128 : }
129 3 : return {};
130 : }
131 :
132 23 : ScenarioSelectionResult ScenarioRegistry::ResolveScenarioSelection(const std::string_view requestedKey,
133 : const std::size_t menuChoiceIndex)
134 : {
135 : static std::once_flag once;
136 23 : std::call_once(once, RegisterBuiltIns);
137 :
138 23 : ScenarioSelectionResult result{};
139 23 : const auto& options = All();
140 23 : if (options.empty())
141 : {
142 0 : return result;
143 : }
144 :
145 23 : if (!requestedKey.empty())
146 : {
147 20 : result.requestedKey = std::string(requestedKey);
148 20 : result.scenario = Create(result.requestedKey);
149 20 : if (result.scenario)
150 : {
151 18 : result.selectedKey = result.requestedKey;
152 18 : return result;
153 : }
154 :
155 2 : result.scenario = options.front().factory();
156 2 : result.selectedKey = options.front().key;
157 2 : result.fallbackUsed = true;
158 2 : result.shouldLogUnknownScenario = true;
159 2 : result.unknownScenarioKey = result.requestedKey;
160 2 : return result;
161 : }
162 :
163 3 : const auto choice = menuChoiceIndex < options.size() ? menuChoiceIndex : 0u;
164 3 : const auto& selected = options[choice];
165 3 : result.requestedKey = selected.key;
166 3 : result.selectedKey = selected.key;
167 3 : result.scenario = selected.factory();
168 3 : result.shouldLogSelectedScenario = true;
169 3 : return result;
170 0 : }
171 :
172 21 : void SetHeadlessRendering(bool enabled)
173 : {
174 21 : HeadlessFlag().store(enabled, std::memory_order_relaxed);
175 21 : }
176 :
177 748 : bool IsHeadlessRendering()
178 : {
179 748 : return HeadlessFlag().load(std::memory_order_relaxed);
180 : }
181 : }
|