./
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
0 / 456
0.0%
Branches:
0 / 794
0.0%
Functions:
0 / 53
0.0%

apps/runner/src/eu/runner/main.cc
Line Branch Exec Source
1 #include <utility>
2
3 // todo(Gustav): look into glad support
4 #define SDL_FUNCTION_POINTER_IS_VOID_POINTER
5
6 #include "SDL.h"
7
8 // #include <string>
9
10 #include "eu/log/log.h"
11
12 #include "eu/core/geom.builder.h"
13 #include "eu/core/geom.h"
14
15 #include "eu/io/file.h"
16 #include "eu/io/mesh.h"
17
18 #include "eu/render/canvas.h"
19 #include "eu/render/state.h"
20 #include "eu/render/font.h"
21 #include "eu/render/opengl_utils.h"
22 #include "eu/render/texture.io.h"
23 #include "eu/render/camera.h"
24 #include "eu/render/compiledmesh.h"
25 #include "eu/render/enable_high_performance_graphics.h"
26 #include "eu/render/postproc.h"
27 #include "eu/render/renderer.h"
28 #include "eu/render/world.h"
29
30 #include "eu/runner/script.h"
31 #include "eu/runner/input.h"
32 #include "eu/runner/entity.h"
33
34 #include "eu/kdl/kdl.h"
35
36
37 #if FF_HAS(EU_DEBUG_RUNNER)
38 #include "eu/imgui/ui.h"
39 #include "eu/imgui/init.h"
40 #include "eu/imgui/debug.h"
41
42 #include "dear_imgui/imgui.h"
43 #include "dear_imgui/backends/imgui_impl_sdl3.h"
44 #include "dear_imgui/backends/imgui_impl_opengl3.h"
45 #endif
46
47
48 ENABLE_HIGH_PERFORMANCE_GRAPHICS
49
50 using namespace eu;
51 using namespace std::string_view_literals;
52
53 // todo(Gustav): move to io
54 ✗ eu::render::ShaderSource load_shader(const std::string& name)
55 {
56 return {
57
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 21 not taken.
✗ .vertex = eu::io::string_from_file(name + ".vert.glsl"),
58 ✗ .fragment = eu::io::string_from_file(name + ".frag.glsl")
59 ✗ };
60
0/10
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 19 not taken.
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 15 not taken.
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 13 not taken.
✗ Branch 8 → 9 not taken.
✗ Branch 8 → 10 not taken.
✗ Branch 16 → 17 not taken.
✗ Branch 16 → 18 not taken.
✗ }
61
62 ✗ eu::Size size_from_v2(const eu::v2& v)
63 {
64 ✗ return eu::Size{ .width = eu::int_from_float(v.x), .height = eu::int_from_float(v.y) };
65 }
66
67 ✗ std::shared_ptr<eu::render::Texture2d> load_texture(const std::string& path, eu::render::ColorData cd,
68 eu::render::TextureEdge texture_edge = eu::render::TextureEdge::repeat,
69 eu::render::Transparency transparency = eu::render::Transparency::exclude)
70 {
71
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 19 not taken.
✗ const auto bin = eu::io::bytes_from_file(path);
72 return std::make_shared<eu::render::Texture2d>(
73
0/2
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 15 not taken.
✗ load_image_from_embedded(SEND_DEBUG_LABEL_MANY(path) embedded_binary { reinterpret_cast<const unsigned int*>(bin.data()), static_cast<unsigned int>(bin.size()) }, texture_edge, eu::render::TextureRenderStyle::mipmap, transparency, cd)
74
0/2
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 13 not taken.
✗ );
75 ✗ }
76
77 ✗ std::shared_ptr<eu::render::Texture2d> create_texture(DEBUG_LABEL_ARG_MANY eu::core::SingleColor pixel_color, eu::render::ColorData cd)
78 {
79 return std::make_shared<eu::render::Texture2d>(
80
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 10 not taken.
✗ load_image_from_color(SEND_DEBUG_LABEL_MANY(debug_label) pixel_color, eu::render::TextureEdge::repeat, eu::render::TextureRenderStyle::pixel, eu::render::Transparency::exclude, cd)
81
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 8 not taken.
✗ );
82 }
83
84 enum class RotationType
85 {
86 a, b, c, d
87 };
88
89 ✗ RotationType ypr_from_kdl_rotation(int rot)
90 {
91
0/5
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 4 not taken.
✗ Branch 2 → 5 not taken.
✗ Branch 2 → 6 not taken.
✗ Branch 2 → 7 not taken.
✗ switch (rot)
92 {
93 ✗ case 22: // 10x
94 ✗ return RotationType::a;
95 ✗ case 10: // 104x
96 ✗ return RotationType::b;
97 ✗ case 16: // 15x
98 ✗ return RotationType::c;
99 ✗ case 0: // 25x
100 ✗ return RotationType::d;
101 ✗ default:
102
0/2
✗ Branch 8 → 9 not taken.
✗ Branch 8 → 12 not taken.
✗ DIE("invalid rotation");
103 ✗ return RotationType::a;
104 }
105 }
106
107 struct TileRotation
108 {
109 Ypr rotation;
110 v3 offset;
111 };
112
113 struct RotationData
114 {
115 TileRotation a = {.rotation = {.yaw = 90_deg, .pitch = 0_deg, .roll = 0_deg }, .offset = zero3f };
116 TileRotation b = {.rotation = {.yaw = 180_deg, .pitch = 0_deg, .roll = 0_deg }, .offset = zero3f };
117 TileRotation c = {.rotation = {.yaw = -90_deg, .pitch = 0_deg, .roll = 0_deg }, .offset = zero3f };
118 TileRotation d = {.rotation = {.yaw = 0_deg, .pitch = 0_deg, .roll = 0_deg }, .offset = zero3f };
119
120 ✗ [[nodiscard]] const TileRotation& get_rotation(RotationType type) const
121 {
122
0/5
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 4 not taken.
✗ Branch 2 → 5 not taken.
✗ Branch 2 → 6 not taken.
✗ Branch 2 → 7 not taken.
✗ switch (type)
123 {
124 ✗ case RotationType::a:
125 ✗ return a;
126 ✗ case RotationType::b:
127 ✗ return b;
128 ✗ case RotationType::c:
129 ✗ return c;
130 ✗ case RotationType::d:
131 ✗ return d;
132 ✗ default:
133
0/2
✗ Branch 8 → 9 not taken.
✗ Branch 8 → 12 not taken.
✗ DIE("unknown rotation type");
134 ✗ return a;
135 }
136 }
137 };
138
139 struct Tile
140 {
141 RotationType type;
142 v3 position;
143 std::unique_ptr<render::MeshInWorld> mesh;
144
145 ✗ void update(const RotationData& rotation_data)
146 {
147 ✗ const auto& rotation = rotation_data.get_rotation(type);
148
0/6
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 8 not taken.
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 8 not taken.
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 8 not taken.
✗ mesh->set_transform(eu::render::transform_from_rotation(position + rotation.offset, rotation.rotation));
149 ✗ }
150 };
151
152 struct Level
153 {
154 std::unordered_map<std::string, std::unique_ptr<eu::render::CompiledMesh>> meshes;
155 std::vector<Tile> tiles;
156 RotationData rotation_data;
157
158 ✗ void load(const std::string& path, render::World* world, std::shared_ptr<render::DefaultMaterial> material, const core::CompiledGeomVertexAttributes& layout)
159 {
160 // todo(Gustav): this is very hacky but just something so we can play a level
161 ✗ v3 offset = { 0.0f, 0.0f, 0.0f };
162 ✗ v3 size = { 1.0f, 1.0f, 1.0f };
163
164
0/4
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 151 not taken.
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 149 not taken.
✗ const auto parsed = kdl::parse(eu::io::string_from_file(path));
165
0/2
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 13 not taken.
✗ if (parsed.document.has_value() == false)
166 {
167
0/6
✗ Branch 8 → 9 not taken.
✗ Branch 8 → 209 not taken.
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 154 not taken.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 152 not taken.
✗ LOG_WARN("Failed to parse '{}': {}", path, parsed.error);
168 ✗ return;
169 }
170
171 ✗ const auto& doc = *parsed.document;
172
173
0/2
✗ Branch 141 → 16 not taken.
✗ Branch 141 → 142 not taken.
✗ for (const auto& node: doc)
174 {
175
0/4
✗ Branch 18 → 19 not taken.
✗ Branch 18 → 208 not taken.
✗ Branch 19 → 20 not taken.
✗ Branch 19 → 37 not taken.
✗ if (node.name == "mesh")
176 {
177
0/4
✗ Branch 21 → 22 not taken.
✗ Branch 21 → 167 not taken.
✗ Branch 22 → 23 not taken.
✗ Branch 22 → 167 not taken.
✗ const auto key = node.args[0].as_string();
178
0/4
✗ Branch 24 → 25 not taken.
✗ Branch 24 → 165 not taken.
✗ Branch 25 → 26 not taken.
✗ Branch 25 → 165 not taken.
✗ const auto file = node.args[1].as_string();
179
180
0/2
✗ Branch 26 → 27 not taken.
✗ Branch 26 → 163 not taken.
✗ const auto mesh = eu::io::mesh_from_file(file);
181
0/2
✗ Branch 27 → 28 not taken.
✗ Branch 27 → 161 not taken.
✗ auto compiled = eu::render::compile_mesh(USE_DEBUG_LABEL_MANY(file) mesh, layout);
182
183
0/4
✗ Branch 28 → 29 not taken.
✗ Branch 28 → 158 not taken.
✗ Branch 29 → 30 not taken.
✗ Branch 29 → 156 not taken.
✗ meshes[key] = std::make_unique<eu::render::CompiledMesh>(compiled);
184 ✗ }
185
0/4
✗ Branch 37 → 38 not taken.
✗ Branch 37 → 208 not taken.
✗ Branch 38 → 39 not taken.
✗ Branch 38 → 53 not taken.
✗ else if (node.name == "offset")
186 {
187
0/4
✗ Branch 40 → 41 not taken.
✗ Branch 40 → 168 not taken.
✗ Branch 41 → 42 not taken.
✗ Branch 41 → 168 not taken.
✗ const auto x = node.args[0].as_number().as<float>().value_or(0.0f);
188
0/4
✗ Branch 44 → 45 not taken.
✗ Branch 44 → 169 not taken.
✗ Branch 45 → 46 not taken.
✗ Branch 45 → 169 not taken.
✗ const auto y = node.args[1].as_number().as<float>().value_or(0.0f);
189
0/4
✗ Branch 48 → 49 not taken.
✗ Branch 48 → 170 not taken.
✗ Branch 49 → 50 not taken.
✗ Branch 49 → 170 not taken.
✗ const auto z = node.args[2].as_number().as<float>().value_or(0.0f);
190 ✗ offset = { x, y, z };
191 }
192
0/4
✗ Branch 53 → 54 not taken.
✗ Branch 53 → 208 not taken.
✗ Branch 54 → 55 not taken.
✗ Branch 54 → 69 not taken.
✗ else if (node.name == "cell_size")
193 {
194
0/4
✗ Branch 56 → 57 not taken.
✗ Branch 56 → 171 not taken.
✗ Branch 57 → 58 not taken.
✗ Branch 57 → 171 not taken.
✗ const auto x = node.args[0].as_number().as<float>().value_or(0.0f);
195
0/4
✗ Branch 60 → 61 not taken.
✗ Branch 60 → 172 not taken.
✗ Branch 61 → 62 not taken.
✗ Branch 61 → 172 not taken.
✗ const auto y = node.args[1].as_number().as<float>().value_or(0.0f);
196
0/4
✗ Branch 64 → 65 not taken.
✗ Branch 64 → 173 not taken.
✗ Branch 65 → 66 not taken.
✗ Branch 65 → 173 not taken.
✗ const auto z = node.args[2].as_number().as<float>().value_or(0.0f);
197 ✗ size = {x, y, z};
198 }
199
0/4
✗ Branch 69 → 70 not taken.
✗ Branch 69 → 208 not taken.
✗ Branch 70 → 71 not taken.
✗ Branch 70 → 127 not taken.
✗ else if (node.name == "item")
200 {
201
0/4
✗ Branch 72 → 73 not taken.
✗ Branch 72 → 174 not taken.
✗ Branch 73 → 74 not taken.
✗ Branch 73 → 174 not taken.
✗ const auto x = node.args[0].as_number().as<float>().value_or(0.0f);
202
0/4
✗ Branch 76 → 77 not taken.
✗ Branch 76 → 175 not taken.
✗ Branch 77 → 78 not taken.
✗ Branch 77 → 175 not taken.
✗ const auto y = node.args[1].as_number().as<float>().value_or(0.0f);
203
0/4
✗ Branch 80 → 81 not taken.
✗ Branch 80 → 176 not taken.
✗ Branch 81 → 82 not taken.
✗ Branch 81 → 176 not taken.
✗ const auto z = node.args[2].as_number().as<float>().value_or(0.0f);
204
0/8
✗ Branch 85 → 86 not taken.
✗ Branch 85 → 179 not taken.
✗ Branch 86 → 87 not taken.
✗ Branch 86 → 177 not taken.
✗ Branch 87 → 88 not taken.
✗ Branch 87 → 177 not taken.
✗ Branch 88 → 89 not taken.
✗ Branch 88 → 177 not taken.
✗ const auto rotation = node.properties.at("rot").as_number().as<int>().value_or(-1);
205 ✗ const v3 pos = { x*size.x, y*size.y, z*size.z };
206
0/8
✗ Branch 95 → 96 not taken.
✗ Branch 95 → 186 not taken.
✗ Branch 96 → 97 not taken.
✗ Branch 96 → 184 not taken.
✗ Branch 97 → 98 not taken.
✗ Branch 97 → 184 not taken.
✗ Branch 98 → 99 not taken.
✗ Branch 98 → 184 not taken.
✗ const auto item = node.properties.at("item").as_string();
207
0/2
✗ Branch 101 → 102 not taken.
✗ Branch 101 → 201 not taken.
✗ const auto found = meshes.find(item);
208
0/2
✗ Branch 104 → 105 not taken.
✗ Branch 104 → 120 not taken.
✗ if (found != meshes.end())
209 {
210
0/2
✗ Branch 105 → 106 not taken.
✗ Branch 105 → 196 not taken.
✗ auto mesh_in_world = std::make_unique<render::MeshInWorld>();
211
0/2
✗ Branch 106 → 107 not taken.
✗ Branch 106 → 194 not taken.
✗ const auto rotation_type = ypr_from_kdl_rotation(rotation);
212
0/2
✗ Branch 111 → 112 not taken.
✗ Branch 111 → 190 not taken.
✗ mesh_in_world->add_to_world(found->second.get(), world, material);
213
0/4
✗ Branch 115 → 116 not taken.
✗ Branch 115 → 193 not taken.
✗ Branch 116 → 117 not taken.
✗ Branch 116 → 193 not taken.
✗ tiles.emplace_back(rotation_type, offset + pos, std::move(mesh_in_world))
214
0/2
✗ Branch 117 → 118 not taken.
✗ Branch 117 → 193 not taken.
✗ .update(rotation_data);
215 ✗ }
216 else
217 {
218
0/6
✗ Branch 120 → 121 not taken.
✗ Branch 120 → 201 not taken.
✗ Branch 121 → 122 not taken.
✗ Branch 121 → 199 not taken.
✗ Branch 122 → 123 not taken.
✗ Branch 122 → 197 not taken.
✗ LOG_WARN("Mesh '{}' not found for item in level file", item);
219 }
220 ✗ }
221 else
222 {
223
0/6
✗ Branch 127 → 128 not taken.
✗ Branch 127 → 208 not taken.
✗ Branch 128 → 129 not taken.
✗ Branch 128 → 206 not taken.
✗ Branch 129 → 130 not taken.
✗ Branch 129 → 204 not taken.
✗ LOG_WARN("Unknown node in level file: '{}'", node.name);
224 }
225 }
226
0/2
✗ Branch 144 → 145 not taken.
✗ Branch 144 → 147 not taken.
✗ }
227 };
228
229
230
231 struct Time
232 {
233 Uint64 now = SDL_GetPerformanceCounter();
234 Uint64 last = 0;
235
236
237 ✗ float calculate()
238 {
239 ✗ last = now;
240 ✗ now = SDL_GetPerformanceCounter();
241
242 ✗ const auto diff = static_cast<float>(now - last);
243 ✗ const auto pfc = static_cast<float>(SDL_GetPerformanceFrequency());
244 ✗ return diff / pfc;
245 }
246 };
247
248
249 struct MeshSpatialComponent : runner::SpatialComponent
250 {
251 render::MeshInWorld car;
252
253 eu::v3 position = { 0.0f, -1.8f, -10.0f };
254 eu::Ypr rotation = { .yaw = 30.0_deg, .pitch = 30.0_deg, .roll = 30.0_deg };
255
256 ✗ const char* display() override
257 {
258 ✗ return "MeshSpatialComponent";
259 }
260
261 ✗ void imgui(const imgui::PropTable& table) override
262 {
263 ✗ runner::SpatialComponent::imgui(table);
264 ✗ table.drag("Position", &position);
265 ✗ table.drag("Rotation", &rotation);
266 ✗ }
267
268 ✗ void update_transform()
269 {
270
0/4
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 7 not taken.
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 7 not taken.
✗ set_transform(eu::render::transform_from_rotation(position, rotation));
271 ✗ car.set_transform(get_transform());
272 ✗ }
273
274 ✗ MeshSpatialComponent(render::CompiledMesh* mesh, render::World* render_world, std::shared_ptr<render::Material> material)
275 ✗ {
276
0/2
✗ Branch 8 → 9 not taken.
✗ Branch 8 → 12 not taken.
✗ car.add_to_world(mesh, render_world, std::move(material));
277
0/2
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 15 not taken.
✗ update_transform();
278 ✗ }
279
280 EU_DEC_COMPONENT_TYPE();
281 };
282
283
0/12
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 12 not taken.
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 12 not taken.
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 16 not taken.
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 14 not taken.
✗ Branch 8 → 9 not taken.
✗ Branch 8 → 14 not taken.
✗ Branch 16 → 17 not taken.
✗ Branch 16 → 18 not taken.
✗ EU_IMP_COMPONENT_TYPE(MeshSpatialComponent, runner::Component)
284
285
286 struct InputSystem : runner::EntitySystem
287 {
288 runner::Input* input;
289 MeshSpatialComponent* mesh = nullptr;
290
291 ✗ const char* display() override
292 {
293 ✗ return "Input";
294 }
295
296 ✗ void imgui(const imgui::PropTable& table) override
297 {
298 ✗ table.display("has mesh", mesh != nullptr);
299 ✗ }
300
301 ✗ explicit InputSystem(runner::Input* in) : input(in)
302 {
303 ✗ }
304
305 ✗ runner::UpdateStageAndPrio get_stage() override
306 {
307 return
308 {
309 .stage = runner::UpdateStage::before_physics,
310 .prio = 100
311 ✗ };
312 }
313
314 ✗ void on_root_changed(runner::SpatialComponent*) override
315 {
316 ✗ }
317
318 ✗ void add_component(runner::Component* component) override
319 {
320
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 5 not taken.
✗ if (MeshSpatialComponent* mc = runner::component_cast<MeshSpatialComponent>(component); mc)
321 {
322 ✗ mesh = mc;
323 }
324 ✗ }
325
326 ✗ void update(float dt) override
327 {
328
0/14
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 8 not taken.
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 8 not taken.
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 18 not taken.
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 8 not taken.
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 11 not taken.
✗ Branch 11 → 12 not taken.
✗ Branch 11 → 15 not taken.
✗ Branch 18 → 19 not taken.
✗ Branch 18 → 20 not taken.
✗ if (mesh && input && input->get_current_frame().binds[0].current > 0.5f)
329 {
330
0/4
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 22 not taken.
✗ Branch 13 → 14 not taken.
✗ Branch 13 → 22 not taken.
✗ mesh->position += kk::in * dt;
331 }
332
333
0/2
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 17 not taken.
✗ if (mesh)
334 {
335 ✗ mesh->update_transform();
336 }
337 ✗ }
338 };
339
340 struct TargetComponent : runner::Component
341 {
342 m4 target = m4_identity;
343
344 ✗ const char* display() override
345 {
346 ✗ return "target";
347 }
348
349 ✗ void imgui(const imgui::PropTable& table) override
350 {
351 ✗ table.transform(target);
352 ✗ }
353
354 EU_DEC_COMPONENT_TYPE();
355 };
356
0/12
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 12 not taken.
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 12 not taken.
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 16 not taken.
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 14 not taken.
✗ Branch 8 → 9 not taken.
✗ Branch 8 → 14 not taken.
✗ Branch 16 → 17 not taken.
✗ Branch 16 → 18 not taken.
✗ EU_IMP_COMPONENT_TYPE(TargetComponent, runner::Component)
357
358 struct CameraSpatialComponent : runner::SpatialComponent
359 {
360 ✗ const char* display() override
361 {
362 ✗ return "camera spatial";
363 }
364
365 ✗ void imgui(const imgui::PropTable& table) override
366 {
367 ✗ runner::SpatialComponent::imgui(table);
368 ✗ }
369 // todo(Gustav): add extra camera settings...
370 EU_DEC_COMPONENT_TYPE();
371 };
372
0/12
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 12 not taken.
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 12 not taken.
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 16 not taken.
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 14 not taken.
✗ Branch 8 → 9 not taken.
✗ Branch 8 → 14 not taken.
✗ Branch 16 → 17 not taken.
✗ Branch 16 → 18 not taken.
✗ EU_IMP_COMPONENT_TYPE(CameraSpatialComponent, runner::SpatialComponent);
373
374 struct FollowCameraSystem : runner::EntitySystem
375 {
376 ✗ runner::UpdateStageAndPrio get_stage() override
377 {
378 return
379 {
380 .stage = runner::UpdateStage::after_physics,
381 .prio = 100
382 ✗ };
383 }
384
385 TargetComponent* target = nullptr;
386 CameraSpatialComponent* camera = nullptr;
387
388 ✗ const char* display() override
389 {
390 ✗ return "Follow camera";
391 }
392
393 imgui::ImLog log;
394 imgui::ImTweak tweak;
395 Rui offset = {.right = 0, .up = 3, .in = -10};
396 ✗ void imgui(const imgui::PropTable& table) override
397 {
398 ✗ table.display("Target", target != nullptr);
399 ✗ table.display("camera", camera != nullptr);
400 ✗ table.gear("Offset", &offset);
401
402 ✗ tweak.draw();
403 ✗ log.draw();
404 ✗ }
405
406 ✗ void on_root_changed(runner::SpatialComponent*) override
407 {
408 ✗ }
409
410 ✗ void add_component(runner::Component* component) override
411 {
412
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 9 not taken.
✗ if (auto* tar = runner::component_cast<TargetComponent>(component))
413 {
414 ✗ target = tar;
415
0/4
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 19 not taken.
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 17 not taken.
✗ LOG_INFO("Follow camera found target");
416 }
417
0/2
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 16 not taken.
✗ else if (auto* cam = runner::component_cast<CameraSpatialComponent>(component))
418 {
419 ✗ camera = cam;
420
0/4
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 23 not taken.
✗ Branch 13 → 14 not taken.
✗ Branch 13 → 21 not taken.
✗ LOG_INFO("Follow camera found camera");
421 }
422 ✗ }
423 ✗ void update(float) override
424 {
425
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 38 not taken.
✗ log.begin();
426
427
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 5 not taken.
✗ if (!target) { return; }
428
0/2
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 7 not taken.
✗ if (!camera) { return; }
429
430
0/2
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 38 not taken.
✗ const auto tr = transform_from_matrix(target->target);
431
432 ✗ const auto focus = tr.position;
433
0/2
✗ Branch 8 → 9 not taken.
✗ Branch 8 → 38 not taken.
✗ const auto o_rot = Ypr::from(tr.rotation);
434
0/6
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 29 not taken.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 29 not taken.
✗ Branch 11 → 12 not taken.
✗ Branch 11 → 29 not taken.
✗ const auto pos_from_focus = m4::from(Q::from(Ypr{.yaw = o_rot.yaw})).get_transformed(offset);
435
0/2
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 38 not taken.
✗ const auto pos = focus + pos_from_focus;
436
0/2
✗ Branch 13 → 14 not taken.
✗ Branch 13 → 38 not taken.
✗ const auto look_at = Q::look_at(pos, focus, kk::up);
437
0/2
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 17 not taken.
✗ if (!look_at)
438 {
439 ✗ return;
440 }
441
0/4
✗ Branch 18 → 19 not taken.
✗ Branch 18 → 34 not taken.
✗ Branch 19 → 20 not taken.
✗ Branch 19 → 32 not taken.
✗ log.add(fmt::format("updated rotation: {}", *look_at));
442
443
0/6
✗ Branch 22 → 23 not taken.
✗ Branch 22 → 37 not taken.
✗ Branch 23 → 24 not taken.
✗ Branch 23 → 36 not taken.
✗ Branch 24 → 25 not taken.
✗ Branch 24 → 36 not taken.
✗ const auto mat = m4::from_translation(pos) * m4::from(*look_at);
444
0/2
✗ Branch 25 → 26 not taken.
✗ Branch 25 → 38 not taken.
✗ camera->set_transform(mat);
445 }
446 };
447
448 namespace tag
449 {
450 constexpr Hsh CameraSpatialSource = "camera_spatial_src_tag"sv;
451 constexpr Hsh CameraDestination = "camera_dst_tag"sv;
452 }
453
454 struct UpdateCameraTarget : runner::WorldSystem
455 {
456 runner::SpatialComponent* src_entity = nullptr;
457 TargetComponent* dst_entity = nullptr;
458
459 ✗ const char* display() override
460 {
461 ✗ return "Update camera target";
462 }
463
464 ✗ void imgui(const imgui::PropTable& table) override
465 {
466 ✗ table.display("src entity", src_entity != nullptr);
467 ✗ table.display("dst entity", dst_entity != nullptr);
468 ✗ }
469
470 ✗ runner::UpdateStageAndPrio get_stage() override
471 {
472 return {
473 .stage = runner::UpdateStage::after_physics,
474 .prio = 100
475 ✗ };
476 }
477
478 ✗ void on_root_changed(runner::Entity* entity, runner::SpatialComponent* component) override
479 {
480
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 9 not taken.
✗ if (entity->has_tag(tag::CameraSpatialSource))
481 {
482
0/4
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 12 not taken.
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 10 not taken.
✗ LOG_INFO("Found spatial source");
483 ✗ src_entity = component;
484 }
485 ✗ }
486
487 ✗ void add_component(runner::Entity* entity, runner::Component* component) override
488 {
489
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 11 not taken.
✗ if (entity->has_tag(tag::CameraDestination))
490 {
491
0/2
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 11 not taken.
✗ if (auto* target = runner::component_cast<TargetComponent>(component))
492 {
493
0/4
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 14 not taken.
✗ Branch 8 → 9 not taken.
✗ Branch 8 → 12 not taken.
✗ LOG_INFO("Found camera destination on {}", entity->name);
494 ✗ dst_entity = target;
495 }
496 }
497 ✗ }
498
499 ✗ void update(float) override
500 {
501
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 4 not taken.
✗ if (!src_entity) { return; }
502
0/2
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 6 not taken.
✗ if (!dst_entity) { return; }
503
504 ✗ dst_entity->target = src_entity->get_transform();
505 }
506 };
507
508 struct CameraFetcherSystem : runner::WorldSystem
509 {
510 ✗ runner::UpdateStageAndPrio get_stage() override
511 {
512 return {
513 .stage = runner::UpdateStage::before_render,
514 .prio = 100
515 ✗ };
516 }
517
518 CameraSpatialComponent* camera = nullptr;
519 m4 transform = m4_identity;
520
521 ✗ const char* display() override
522 {
523 ✗ return "Camera fetcher";
524 }
525
526 ✗ void imgui(const imgui::PropTable& table) override
527 {
528 ✗ table.display("camera", camera != nullptr);
529 ✗ table.transform(transform);
530 ✗ }
531
532 ✗ void add_component(runner::Entity*, runner::Component* component) override
533 {
534
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 9 not taken.
✗ if (auto cam = runner::component_cast<CameraSpatialComponent>(component))
535 {
536
0/4
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 12 not taken.
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 10 not taken.
✗ LOG_INFO("Fetcher found camera");
537 ✗ camera = cam;
538 }
539 ✗ }
540
541 ✗ void on_root_changed(runner::Entity*, runner::SpatialComponent*) override
542 {
543 ✗ }
544
545 ✗ void update(float) override
546 {
547
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 4 not taken.
✗ if (!camera) return;
548 ✗ transform = camera->get_transform();
549 }
550
551 ✗ render::Camera fetch() const
552 {
553 ✗ render::Camera r;
554 ✗ r.world_from_view = transform;
555 ✗ return r;
556 }
557 };
558
559 ✗ int main(int, char**)
560 {
561 ✗ const char* glsl_version = "#version 130";
562
563 // todo(Gustav): set app metadata
564
565
0/4
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 816 not taken.
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 10 not taken.
✗ if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) == false)
566 {
567
0/8
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 816 not taken.
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 475 not taken.
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 474 not taken.
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 472 not taken.
✗ LOG_ERR("Error initializing SDL: {}", SDL_GetError());
568 ✗ return -1;
569 }
570
571 {
572 ✗ const auto str = [](int version)
573 {
574 ✗ const auto major = SDL_VERSIONNUM_MAJOR(version);
575 ✗ const auto minor = SDL_VERSIONNUM_MINOR(version);
576 ✗ const auto micro = SDL_VERSIONNUM_MICRO(version);
577
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 5 not taken.
✗ return fmt::format("{0}.{1}.{2}", major, minor, micro);
578 };
579
0/2
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 487 not taken.
✗ const int version_loaded = SDL_GetVersion();
580
0/10
✗ Branch 11 → 12 not taken.
✗ Branch 11 → 487 not taken.
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 485 not taken.
✗ Branch 13 → 14 not taken.
✗ Branch 13 → 482 not taken.
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 479 not taken.
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 477 not taken.
✗ LOG_INFO("SDL {0} initialized with {1}", str(SDL_VERSION), str(version_loaded));
581 }
582
583 // todo(Gustav): move to some shared sdl lib instead? as this shouldn't use dear imgui and 100% rely on sdl
584
0/2
✗ Branch 19 → 20 not taken.
✗ Branch 19 → 816 not taken.
✗ const auto app_scale = eu::imgui::calculate_app_scale();
585
0/2
✗ Branch 20 → 21 not taken.
✗ Branch 20 → 816 not taken.
✗ auto [window_width, window_height] = eu::imgui::rescale_window(1280, 720, app_scale);
586
587 #if defined(__APPLE__)
588 // GL 3.2 Core + GLSL 150
589 const char* glsl_version = "#version 150";
590 SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); // Always required on Mac
591 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
592 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
593 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
594 #else
595 // GL 3.0 + GLSL 130
596 // const char* glsl_version = "#version 130";
597
0/2
✗ Branch 23 → 24 not taken.
✗ Branch 23 → 816 not taken.
✗ SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
598
0/2
✗ Branch 24 → 25 not taken.
✗ Branch 24 → 816 not taken.
✗ SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
599
0/2
✗ Branch 25 → 26 not taken.
✗ Branch 25 → 816 not taken.
✗ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
600
0/2
✗ Branch 26 → 27 not taken.
✗ Branch 26 → 816 not taken.
✗ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); // was 0 in dear imgui example??
601 #endif
602
603
0/2
✗ Branch 27 → 28 not taken.
✗ Branch 27 → 816 not taken.
✗ SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
604
0/2
✗ Branch 28 → 29 not taken.
✗ Branch 28 → 816 not taken.
✗ SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
605
606
0/2
✗ Branch 29 → 30 not taken.
✗ Branch 29 → 816 not taken.
✗ SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
607
0/2
✗ Branch 30 → 31 not taken.
✗ Branch 30 → 816 not taken.
✗ SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
608
609
0/2
✗ Branch 31 → 32 not taken.
✗ Branch 31 → 816 not taken.
✗ SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
610
0/2
✗ Branch 32 → 33 not taken.
✗ Branch 32 → 816 not taken.
✗ SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
611
0/2
✗ Branch 33 → 34 not taken.
✗ Branch 33 → 816 not taken.
✗ SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
612
0/2
✗ Branch 34 → 35 not taken.
✗ Branch 34 → 816 not taken.
✗ SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
613
614
0/2
✗ Branch 35 → 36 not taken.
✗ Branch 35 → 816 not taken.
✗ SDL_Window* window = SDL_CreateWindow("Runner",
615 window_width, window_height, SDL_WINDOW_OPENGL | SDL_WINDOW_HIGH_PIXEL_DENSITY | SDL_WINDOW_RESIZABLE);
616
0/2
✗ Branch 36 → 37 not taken.
✗ Branch 36 → 43 not taken.
✗ if (!window) {
617
0/8
✗ Branch 37 → 38 not taken.
✗ Branch 37 → 816 not taken.
✗ Branch 38 → 39 not taken.
✗ Branch 38 → 491 not taken.
✗ Branch 39 → 40 not taken.
✗ Branch 39 → 490 not taken.
✗ Branch 40 → 41 not taken.
✗ Branch 40 → 488 not taken.
✗ LOG_ERR("Error creating window: {}", SDL_GetError());
618 ✗ return -1;
619 }
620
621
0/2
✗ Branch 43 → 44 not taken.
✗ Branch 43 → 816 not taken.
✗ auto* glContext = SDL_GL_CreateContext(window);
622
0/2
✗ Branch 44 → 45 not taken.
✗ Branch 44 → 816 not taken.
✗ SDL_GetWindowSize(window, &window_width, &window_height);
623
0/2
✗ Branch 45 → 46 not taken.
✗ Branch 45 → 52 not taken.
✗ if (!glContext) {
624
0/8
✗ Branch 46 → 47 not taken.
✗ Branch 46 → 816 not taken.
✗ Branch 47 → 48 not taken.
✗ Branch 47 → 496 not taken.
✗ Branch 48 → 49 not taken.
✗ Branch 48 → 495 not taken.
✗ Branch 49 → 50 not taken.
✗ Branch 49 → 493 not taken.
✗ LOG_ERR("Error creating gl context: {}", SDL_GetError());
625 ✗ return -1;
626 }
627
628 /* OpenGL setup */
629
0/2
✗ Branch 52 → 53 not taken.
✗ Branch 52 → 816 not taken.
✗ const int glad_result = gladLoadGLLoader(SDL_GL_GetProcAddress);
630
0/2
✗ Branch 53 → 54 not taken.
✗ Branch 53 → 59 not taken.
✗ if (glad_result == 0)
631 {
632
0/6
✗ Branch 54 → 55 not taken.
✗ Branch 54 → 816 not taken.
✗ Branch 55 → 56 not taken.
✗ Branch 55 → 500 not taken.
✗ Branch 56 → 57 not taken.
✗ Branch 56 → 498 not taken.
✗ LOG_ERR("Failed to init glad, error: {0}", glad_result);
633 ✗ return -1;
634 }
635
636 {
637
0/2
✗ Branch 59 → 60 not taken.
✗ Branch 59 → 91 not taken.
✗ if (GLAD_GL_VERSION_1_0) {
638
0/4
✗ Branch 62 → 63 not taken.
✗ Branch 62 → 502 not taken.
✗ Branch 63 → 64 not taken.
✗ Branch 63 → 502 not taken.
✗ const std::string gl_vendor = reinterpret_cast<const char*>(glGetString(GL_VENDOR));
639
0/4
✗ Branch 67 → 68 not taken.
✗ Branch 67 → 505 not taken.
✗ Branch 68 → 69 not taken.
✗ Branch 68 → 505 not taken.
✗ const std::string gl_renderer = reinterpret_cast<const char*>(glGetString(GL_RENDERER));
640
0/4
✗ Branch 72 → 73 not taken.
✗ Branch 72 → 508 not taken.
✗ Branch 73 → 74 not taken.
✗ Branch 73 → 508 not taken.
✗ const std::string gl_version = reinterpret_cast<const char*>(glGetString(GL_VERSION));
641
0/6
✗ Branch 75 → 76 not taken.
✗ Branch 75 → 523 not taken.
✗ Branch 76 → 77 not taken.
✗ Branch 76 → 513 not taken.
✗ Branch 77 → 78 not taken.
✗ Branch 77 → 511 not taken.
✗ LOG_INFO("OpenGL Vendor: {0}", gl_vendor);
642
0/6
✗ Branch 79 → 80 not taken.
✗ Branch 79 → 523 not taken.
✗ Branch 80 → 81 not taken.
✗ Branch 80 → 517 not taken.
✗ Branch 81 → 82 not taken.
✗ Branch 81 → 515 not taken.
✗ LOG_INFO("OpenGL Renderer: {0}", gl_renderer);
643
0/6
✗ Branch 83 → 84 not taken.
✗ Branch 83 → 523 not taken.
✗ Branch 84 → 85 not taken.
✗ Branch 84 → 521 not taken.
✗ Branch 85 → 86 not taken.
✗ Branch 85 → 519 not taken.
✗ LOG_INFO("OpenGL Version: {0}", gl_version);
644 ✗ }
645
646
0/2
✗ Branch 91 → 92 not taken.
✗ Branch 91 → 103 not taken.
✗ if (GLAD_GL_VERSION_2_0) {
647
0/4
✗ Branch 94 → 95 not taken.
✗ Branch 94 → 530 not taken.
✗ Branch 95 → 96 not taken.
✗ Branch 95 → 530 not taken.
✗ const std::string gl_shading_language_version = reinterpret_cast<const char*>(glGetString(GL_SHADING_LANGUAGE_VERSION));
648
0/6
✗ Branch 97 → 98 not taken.
✗ Branch 97 → 537 not taken.
✗ Branch 98 → 99 not taken.
✗ Branch 98 → 535 not taken.
✗ Branch 99 → 100 not taken.
✗ Branch 99 → 533 not taken.
✗ LOG_INFO("Version GLSL: {0}", gl_shading_language_version);
649 ✗ }
650 }
651
652
0/2
✗ Branch 103 → 104 not taken.
✗ Branch 103 → 109 not taken.
✗ if (!GLAD_GL_VERSION_4_3)
653 {
654
0/6
✗ Branch 104 → 105 not taken.
✗ Branch 104 → 816 not taken.
✗ Branch 105 → 106 not taken.
✗ Branch 105 → 542 not taken.
✗ Branch 106 → 107 not taken.
✗ Branch 106 → 540 not taken.
✗ LOG_ERR("OpenGL 4.3 not supported ({0}.{1} detected), aborting...", GLVersion.major, GLVersion.minor);
655 ✗ return -1;
656 }
657
658 #if FF_HAS(EU_DEBUG_RUNNER)
659
0/2
✗ Branch 109 → 110 not taken.
✗ Branch 109 → 816 not taken.
✗ IMGUI_CHECKVERSION();
660
0/2
✗ Branch 110 → 111 not taken.
✗ Branch 110 → 816 not taken.
✗ ImGui::CreateContext();
661
0/2
✗ Branch 111 → 112 not taken.
✗ Branch 111 → 816 not taken.
✗ ImGuiIO& io = ImGui::GetIO(); (void)io;
662
0/2
✗ Branch 112 → 113 not taken.
✗ Branch 112 → 816 not taken.
✗ ImGui::StyleColorsDark();
663
0/2
✗ Branch 113 → 114 not taken.
✗ Branch 113 → 816 not taken.
✗ imgui::setup_colors();
664
0/4
✗ Branch 114 → 115 not taken.
✗ Branch 114 → 816 not taken.
✗ Branch 115 → 116 not taken.
✗ Branch 115 → 121 not taken.
✗ if (false == ImGui_ImplSDL3_InitForOpenGL(window, glContext))
665 {
666
0/6
✗ Branch 116 → 117 not taken.
✗ Branch 116 → 816 not taken.
✗ Branch 117 → 118 not taken.
✗ Branch 117 → 546 not taken.
✗ Branch 118 → 119 not taken.
✗ Branch 118 → 544 not taken.
✗ LOG_ERR("Failed to init ImGui SDL3 backend");
667 ✗ return -1;
668 }
669
0/4
✗ Branch 121 → 122 not taken.
✗ Branch 121 → 816 not taken.
✗ Branch 122 → 123 not taken.
✗ Branch 122 → 128 not taken.
✗ if (false == ImGui_ImplOpenGL3_Init(glsl_version))
670 {
671
0/6
✗ Branch 123 → 124 not taken.
✗ Branch 123 → 816 not taken.
✗ Branch 124 → 125 not taken.
✗ Branch 124 → 550 not taken.
✗ Branch 125 → 126 not taken.
✗ Branch 125 → 548 not taken.
✗ LOG_ERR("Failed to init ImGui OpenGL3 backend");
672 ✗ return -1;
673 }
674
675
0/2
✗ Branch 128 → 129 not taken.
✗ Branch 128 → 816 not taken.
✗ eu::imgui::setup_scale(app_scale);
676
677 ✗ bool show_demo_window = true;
678 #endif
679
680 ✗ eu::render::State states;
681
0/2
✗ Branch 129 → 130 not taken.
✗ Branch 129 → 816 not taken.
✗ eu::render::Render2 render{ &states };
682
683
0/2
✗ Branch 130 → 131 not taken.
✗ Branch 130 → 814 not taken.
✗ eu::render::Assets assets;
684
685
0/4
✗ Branch 134 → 135 not taken.
✗ Branch 134 → 554 not taken.
✗ Branch 135 → 136 not taken.
✗ Branch 135 → 552 not taken.
✗ assets.black = create_texture(SEND_DEBUG_LABEL_MANY("black") eu::core::color_from_rgba(0, 0, 0, 255), eu::render::ColorData::dont_care);
686
0/4
✗ Branch 143 → 144 not taken.
✗ Branch 143 → 561 not taken.
✗ Branch 144 → 145 not taken.
✗ Branch 144 → 559 not taken.
✗ assets.white = create_texture(SEND_DEBUG_LABEL_MANY("white") eu::core::color_from_rgba(255, 255, 255, 255), eu::render::ColorData::dont_care);
687
688
0/4
✗ Branch 151 → 152 not taken.
✗ Branch 151 → 568 not taken.
✗ Branch 152 → 153 not taken.
✗ Branch 152 → 566 not taken.
✗ assets.default_shader_source = load_shader("default_shader");
689
0/4
✗ Branch 159 → 160 not taken.
✗ Branch 159 → 575 not taken.
✗ Branch 160 → 161 not taken.
✗ Branch 160 → 573 not taken.
✗ assets.skybox_shader_source = load_shader("skybox");
690
691
0/4
✗ Branch 167 → 168 not taken.
✗ Branch 167 → 582 not taken.
✗ Branch 168 → 169 not taken.
✗ Branch 168 → 580 not taken.
✗ assets.pp_vert_glsl = eu::io::string_from_file("pp_vert.glsl");
692
0/4
✗ Branch 175 → 176 not taken.
✗ Branch 175 → 589 not taken.
✗ Branch 176 → 177 not taken.
✗ Branch 176 → 587 not taken.
✗ assets.pp_realize_frag_glsl = eu::io::string_from_file("pp_realize.frag.glsl");
693
0/4
✗ Branch 183 → 184 not taken.
✗ Branch 183 → 596 not taken.
✗ Branch 184 → 185 not taken.
✗ Branch 184 → 594 not taken.
✗ assets.pp_extract_frag_glsl = eu::io::string_from_file("pp_extract.frag.glsl");
694
0/4
✗ Branch 191 → 192 not taken.
✗ Branch 191 → 603 not taken.
✗ Branch 192 → 193 not taken.
✗ Branch 192 → 601 not taken.
✗ assets.pp_ping_pong_blur_frag_glsl = eu::io::string_from_file("pp_ping_pong_blur.frag.glsl");
695
696
0/2
✗ Branch 197 → 198 not taken.
✗ Branch 197 → 608 not taken.
✗ eu::render::Renderer renderer{ &states, &assets, eu::render::RenderSettings{} };
697
0/4
✗ Branch 198 → 199 not taken.
✗ Branch 198 → 810 not taken.
✗ Branch 199 → 200 not taken.
✗ Branch 199 → 201 not taken.
✗ if (renderer.is_loaded() == false)
698 {
699 ✗ return -1;
700 }
701
702 ✗ eu::runner::Input input;
703
0/6
✗ Branch 204 → 205 not taken.
✗ Branch 204 → 611 not taken.
✗ Branch 205 → 206 not taken.
✗ Branch 205 → 609 not taken.
✗ Branch 208 → 209 not taken.
✗ Branch 208 → 210 not taken.
✗ if (false == input.load_from_file("input.kdl"))
704 {
705 ✗ return -1;
706 }
707
708
0/2
✗ Branch 210 → 211 not taken.
✗ Branch 210 → 808 not taken.
✗ eu::runner::Script script;
709
0/6
✗ Branch 213 → 214 not taken.
✗ Branch 213 → 617 not taken.
✗ Branch 214 → 215 not taken.
✗ Branch 214 → 615 not taken.
✗ Branch 217 → 218 not taken.
✗ Branch 217 → 219 not taken.
✗ if (false == script.run_file("main.lax"))
710 {
711 ✗ return -1;
712 }
713
714 ✗ eu::render::World render_world;
715 ✗ eu::render::EffectStack effects;
716
717 #if FF_HAS(EU_DEBUG_RUNNER)
718
0/2
✗ Branch 219 → 220 not taken.
✗ Branch 219 → 802 not taken.
✗ imgui::ImguiShaderCache imgui_shader_cache;
719 #endif
720
721 // add demo world
722 {
723 ✗ constexpr auto PLANE_SIZE = 100.0f;
724 auto plane_geom = eu::render::compile_geom(
725
0/2
✗ Branch 225 → 226 not taken.
✗ Branch 225 → 623 not taken.
✗ USE_DEBUG_LABEL_MANY("plane")
726
0/4
✗ Branch 221 → 222 not taken.
✗ Branch 221 → 631 not taken.
✗ Branch 222 → 223 not taken.
✗ Branch 222 → 629 not taken.
✗ eu::core::geom::create_xz_plane(PLANE_SIZE, PLANE_SIZE, false).to_geom(),
727
0/2
✗ Branch 220 → 221 not taken.
✗ Branch 220 → 635 not taken.
✗ renderer.default_geom_layout()
728
0/2
✗ Branch 226 → 227 not taken.
✗ Branch 226 → 621 not taken.
✗ );
729
730
0/2
✗ Branch 232 → 233 not taken.
✗ Branch 232 → 654 not taken.
✗ auto material = renderer.make_default_material();
731
0/4
✗ Branch 235 → 236 not taken.
✗ Branch 235 → 638 not taken.
✗ Branch 236 → 237 not taken.
✗ Branch 236 → 636 not taken.
✗ material->diffuse = load_texture("sprites/smoke.png", eu::render::ColorData::dont_care);
732 ✗ material->specular = assets.white;
733
734
0/2
✗ Branch 246 → 247 not taken.
✗ Branch 246 → 643 not taken.
✗ auto plane = make_mesh_instance(plane_geom, material);
735
0/2
✗ Branch 249 → 250 not taken.
✗ Branch 249 → 650 not taken.
✗ render_world.meshes.emplace_back(plane);
736
0/2
✗ Branch 251 → 252 not taken.
✗ Branch 251 → 649 not taken.
✗ plane->transform = eu::m4::from_translation({ 0.0f, -3.0f, 0.0f });
737
738
0/2
✗ Branch 253 → 254 not taken.
✗ Branch 253 → 650 not taken.
✗ render_world.lights.point_lights.emplace_back();
739 ✗ }
740
741 // add demo mesh
742 ✗ eu::runner::World runner_world;
743 ✗ std::unordered_map<std::string, std::unique_ptr<render::CompiledMesh>> meshes;
744 ✗ std::unordered_map<std::string, std::shared_ptr<eu::render::Texture2d>> textures;
745
746 ✗ const auto load_mesh = [&](const std::string& f) -> render::CompiledMesh*
747 {
748
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 31 not taken.
✗ const auto found = meshes.find(f);
749
0/2
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 8 not taken.
✗ if (found != meshes.end())
750 {
751 ✗ return found->second.get();
752 }
753
0/2
✗ Branch 8 → 9 not taken.
✗ Branch 8 → 31 not taken.
✗ const auto mesh = eu::io::mesh_from_file(f);
754
0/4
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 24 not taken.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 22 not taken.
✗ const auto compiled = eu::render::compile_mesh(USE_DEBUG_LABEL_MANY(f) mesh, renderer.default_geom_layout());
755
0/2
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 27 not taken.
✗ auto data = std::make_unique<render::CompiledMesh>(compiled);
756 ✗ auto* ret = data.get();
757
0/2
✗ Branch 16 → 17 not taken.
✗ Branch 16 → 25 not taken.
✗ meshes.insert_or_assign(f, std::move(data));
758 ✗ return ret;
759 ✗ };
760
761 ✗ const auto load_color_texture = [&](const std::string& f) -> std::shared_ptr<render::Texture2d>
762 {
763
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 16 not taken.
✗ const auto found = textures.find(f);
764
0/2
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 8 not taken.
✗ if (found != textures.end())
765 {
766 ✗ return found->second;
767 }
768
0/2
✗ Branch 8 → 9 not taken.
✗ Branch 8 → 16 not taken.
✗ const auto loaded = load_texture(f, eu::render::ColorData::color_data);
769
0/2
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 14 not taken.
✗ textures.insert_or_assign(f, loaded);
770 ✗ return loaded;
771 ✗ };
772
773
0/4
✗ Branch 259 → 260 not taken.
✗ Branch 259 → 661 not taken.
✗ Branch 261 → 262 not taken.
✗ Branch 261 → 657 not taken.
✗ runner_world.add_system(std::make_unique<UpdateCameraTarget>());
774
775 CameraFetcherSystem* camera_fetcher;
776 {
777
0/2
✗ Branch 264 → 265 not taken.
✗ Branch 264 → 668 not taken.
✗ auto fetch = std::make_unique<CameraFetcherSystem>();
778 ✗ camera_fetcher = fetch.get();
779
0/2
✗ Branch 269 → 270 not taken.
✗ Branch 269 → 663 not taken.
✗ runner_world.add_system(std::move(fetch));
780 ✗ }
781
782 {
783
0/6
✗ Branch 274 → 275 not taken.
✗ Branch 274 → 677 not taken.
✗ Branch 277 → 278 not taken.
✗ Branch 277 → 671 not taken.
✗ Branch 278 → 279 not taken.
✗ Branch 278 → 669 not taken.
✗ auto* car = runner_world.add_entity("car", { tag::CameraSpatialSource });
784
785 // mesh component
786 // debug/axis.glb
787 // "models/vehicle-truck-purple.glb"
788 // "models/textures/colormap.png"
789 // render::CompiledMesh* mesh, render::World* render_world, std::shared_ptr<render::Material> material
790
0/4
✗ Branch 285 → 286 not taken.
✗ Branch 285 → 684 not taken.
✗ Branch 286 → 287 not taken.
✗ Branch 286 → 682 not taken.
✗ auto* mesh = load_mesh("models/vehicle-truck-purple.glb");
791
0/2
✗ Branch 289 → 290 not taken.
✗ Branch 289 → 710 not taken.
✗ auto material = renderer.make_default_material();
792
0/4
✗ Branch 292 → 293 not taken.
✗ Branch 292 → 690 not taken.
✗ Branch 293 → 294 not taken.
✗ Branch 293 → 688 not taken.
✗ material->diffuse = load_color_texture("models/textures/colormap.png");
793 ✗ material->specular = assets.white;
794
0/2
✗ Branch 301 → 302 not taken.
✗ Branch 301 → 695 not taken.
✗ auto comp = std::make_unique<MeshSpatialComponent>(mesh, &render_world, material);
795
0/2
✗ Branch 303 → 304 not taken.
✗ Branch 303 → 706 not taken.
✗ car->set_root(comp.get());
796
0/2
✗ Branch 307 → 308 not taken.
✗ Branch 307 → 696 not taken.
✗ car->add_component(std::move(comp));
797
798 // input component
799
0/4
✗ Branch 309 → 310 not taken.
✗ Branch 309 → 703 not taken.
✗ Branch 311 → 312 not taken.
✗ Branch 311 → 699 not taken.
✗ car->add_system(std::make_unique<InputSystem>(&input));
800 ✗ }
801 {
802
0/6
✗ Branch 318 → 319 not taken.
✗ Branch 318 → 719 not taken.
✗ Branch 321 → 322 not taken.
✗ Branch 321 → 713 not taken.
✗ Branch 322 → 323 not taken.
✗ Branch 322 → 711 not taken.
✗ auto* cam = runner_world.add_entity("camera", { tag::CameraDestination });
803
804
0/4
✗ Branch 327 → 328 not taken.
✗ Branch 327 → 728 not taken.
✗ Branch 329 → 330 not taken.
✗ Branch 329 → 724 not taken.
✗ cam->add_component(std::make_unique<TargetComponent>());
805
0/2
✗ Branch 332 → 333 not taken.
✗ Branch 332 → 741 not taken.
✗ auto spat = std::make_unique<CameraSpatialComponent>();
806
0/2
✗ Branch 334 → 335 not taken.
✗ Branch 334 → 739 not taken.
✗ cam->set_root(spat.get());
807
0/2
✗ Branch 338 → 339 not taken.
✗ Branch 338 → 730 not taken.
✗ cam->add_component(std::move(spat));
808
0/4
✗ Branch 340 → 341 not taken.
✗ Branch 340 → 737 not taken.
✗ Branch 342 → 343 not taken.
✗ Branch 342 → 733 not taken.
✗ cam->add_system(std::make_unique<FollowCameraSystem>());
809 ✗ }
810
811
0/2
✗ Branch 346 → 347 not taken.
✗ Branch 346 → 794 not taken.
✗ Level level;
812 {
813
0/2
✗ Branch 347 → 348 not taken.
✗ Branch 347 → 763 not taken.
✗ auto material = renderer.make_default_material();
814
0/4
✗ Branch 350 → 351 not taken.
✗ Branch 350 → 744 not taken.
✗ Branch 351 → 352 not taken.
✗ Branch 351 → 742 not taken.
✗ material->diffuse = load_color_texture("models/textures/colormap.png");
815 ✗ material->specular = assets.white;
816
0/6
✗ Branch 359 → 360 not taken.
✗ Branch 359 → 760 not taken.
✗ Branch 363 → 364 not taken.
✗ Branch 363 → 751 not taken.
✗ Branch 364 → 365 not taken.
✗ Branch 364 → 749 not taken.
✗ level.load("level.kdl", &render_world, material, renderer.default_geom_layout());
817 ✗ }
818
819
0/6
✗ Branch 370 → 371 not taken.
✗ Branch 370 → 792 not taken.
✗ Branch 371 → 372 not taken.
✗ Branch 371 → 766 not taken.
✗ Branch 372 → 373 not taken.
✗ Branch 372 → 764 not taken.
✗ LOG_INFO("Runner started");
820 ✗ bool running = true;
821
0/2
✗ Branch 374 → 375 not taken.
✗ Branch 374 → 792 not taken.
✗ Time time;
822
0/2
✗ Branch 447 → 376 not taken.
✗ Branch 447 → 448 not taken.
✗ while (running)
823 {
824
0/2
✗ Branch 376 → 377 not taken.
✗ Branch 376 → 787 not taken.
✗ const auto dt = time.calculate();
825
826
0/2
✗ Branch 377 → 378 not taken.
✗ Branch 377 → 787 not taken.
✗ runner_world.update(runner::UpdateStage::start_frame, dt);
827
828 SDL_Event e;
829
0/2
✗ Branch 378 → 379 not taken.
✗ Branch 378 → 787 not taken.
✗ input.update();
830
0/4
✗ Branch 393 → 394 not taken.
✗ Branch 393 → 787 not taken.
✗ Branch 394 → 380 not taken.
✗ Branch 394 → 395 not taken.
✗ while (SDL_PollEvent(&e) != 0)
831 {
832 #if FF_HAS(EU_DEBUG_RUNNER)
833
0/2
✗ Branch 380 → 381 not taken.
✗ Branch 380 → 787 not taken.
✗ ImGui_ImplSDL3_ProcessEvent(&e);
834 #endif
835
836
0/4
✗ Branch 381 → 382 not taken.
✗ Branch 381 → 384 not taken.
✗ Branch 381 → 390 not taken.
✗ Branch 381 → 391 not taken.
✗ switch (e.type)
837 {
838 ✗ case SDL_EVENT_KEY_DOWN:
839 case SDL_EVENT_KEY_UP:
840
0/2
✗ Branch 382 → 383 not taken.
✗ Branch 382 → 787 not taken.
✗ input.on_key(e.key.key, e.type == SDL_EVENT_KEY_DOWN);
841 ✗ break;
842 ✗ case SDL_EVENT_WINDOW_RESIZED:
843
0/6
✗ Branch 384 → 385 not taken.
✗ Branch 384 → 787 not taken.
✗ Branch 385 → 386 not taken.
✗ Branch 385 → 770 not taken.
✗ Branch 386 → 387 not taken.
✗ Branch 386 → 768 not taken.
✗ LOG_INFO("Resized");
844
0/2
✗ Branch 388 → 389 not taken.
✗ Branch 388 → 787 not taken.
✗ SDL_GetWindowSize(window, &window_width, &window_height);
845 ✗ break;
846 ✗ case SDL_EVENT_QUIT: running = false; break;
847 ✗ default:
848 // ignore other events
849 ✗ break;
850 }
851 }
852
853 // todo(Gustav): lookup unity frame, when updates are called and update stages
854
0/2
✗ Branch 395 → 396 not taken.
✗ Branch 395 → 787 not taken.
✗ runner_world.update(runner::UpdateStage::before_physics, dt);
855
0/2
✗ Branch 396 → 397 not taken.
✗ Branch 396 → 787 not taken.
✗ runner_world.update(runner::UpdateStage::physics, dt);
856
0/2
✗ Branch 397 → 398 not taken.
✗ Branch 397 → 787 not taken.
✗ runner_world.update(runner::UpdateStage::after_physics, dt);
857
858 #if FF_HAS(EU_DEBUG_RUNNER)
859
0/2
✗ Branch 398 → 399 not taken.
✗ Branch 398 → 787 not taken.
✗ ImGui_ImplOpenGL3_NewFrame();
860
0/2
✗ Branch 399 → 400 not taken.
✗ Branch 399 → 787 not taken.
✗ ImGui_ImplSDL3_NewFrame();
861
0/2
✗ Branch 400 → 401 not taken.
✗ Branch 400 → 787 not taken.
✗ ImGui::NewFrame();
862
863
0/2
✗ Branch 401 → 402 not taken.
✗ Branch 401 → 403 not taken.
✗ if (show_demo_window)
864 {
865
0/2
✗ Branch 402 → 403 not taken.
✗ Branch 402 → 787 not taken.
✗ ImGui::ShowDemoWindow(&show_demo_window);
866 }
867
868
0/4
✗ Branch 403 → 404 not taken.
✗ Branch 403 → 787 not taken.
✗ Branch 404 → 405 not taken.
✗ Branch 404 → 417 not taken.
✗ if (ImGui::Begin("Renderer"))
869 {
870
0/4
✗ Branch 405 → 406 not taken.
✗ Branch 405 → 774 not taken.
✗ Branch 407 → 408 not taken.
✗ Branch 407 → 415 not taken.
✗ if (auto prop = eu::imgui::PropTable::begin())
871 {
872
0/2
✗ Branch 408 → 409 not taken.
✗ Branch 408 → 772 not taken.
✗ prop.color("Clear color", &render_world.clear_color);
873
0/2
✗ Branch 409 → 410 not taken.
✗ Branch 409 → 772 not taken.
✗ prop.simple_gamma_slider("Gamma/Brightness", &renderer.settings.gamma);
874
0/2
✗ Branch 410 → 411 not taken.
✗ Branch 410 → 772 not taken.
✗ prop.check("Use bloom", &renderer.settings.use_bloom);
875
0/2
✗ Branch 411 → 412 not taken.
✗ Branch 411 → 772 not taken.
✗ prop.drag("Bloom cutoff", &renderer.settings.bloom_cutoff, 0.001f);
876
0/2
✗ Branch 412 → 413 not taken.
✗ Branch 412 → 772 not taken.
✗ prop.slider("Softness", &renderer.settings.bloom_softness, 0.0f, 1.0f);
877
0/2
✗ Branch 413 → 414 not taken.
✗ Branch 413 → 772 not taken.
✗ prop.drag("Bloom blur steps", &renderer.settings.bloom_blur_steps);
878
0/2
✗ Branch 414 → 415 not taken.
✗ Branch 414 → 772 not taken.
✗ effects.gui(&imgui_shader_cache, prop);
879 ✗ }
880
881
0/2
✗ Branch 416 → 417 not taken.
✗ Branch 416 → 787 not taken.
✗ effects.gui_effects();
882 }
883
0/2
✗ Branch 417 → 418 not taken.
✗ Branch 417 → 787 not taken.
✗ ImGui::End();
884
885 // todo(Gustav): add introspection ui
886
0/4
✗ Branch 418 → 419 not taken.
✗ Branch 418 → 787 not taken.
✗ Branch 419 → 420 not taken.
✗ Branch 419 → 421 not taken.
✗ if (ImGui::Begin("Demo"))
887 {
888
0/2
✗ Branch 420 → 421 not taken.
✗ Branch 420 → 787 not taken.
✗ runner_world.gui();
889 }
890
0/2
✗ Branch 421 → 422 not taken.
✗ Branch 421 → 787 not taken.
✗ ImGui::End();
891 #endif
892
893 ✗ eu::render::RenderCommand cmd{ .states = &states, .render = &render, .size = {.width = window_width, .height = window_height} };
894
895 // clear
896 {
897 {
898 ✗ const auto screen = eu::render::LayoutData{ .style = eu::render::ViewportStyle::extended,
899 .requested_width = 800, .requested_height = 600 };
900
0/2
✗ Branch 422 → 423 not taken.
✗ Branch 422 → 775 not taken.
✗ cmd.clear(eu::colors::black, screen);
901 }
902 }
903
904
0/2
✗ Branch 423 → 424 not taken.
✗ Branch 423 → 787 not taken.
✗ runner_world.update(runner::UpdateStage::before_render, dt);
905
906 // render 3d world
907 {
908 ✗ const auto screen = eu::render::LayoutData{ .style = eu::render::ViewportStyle::black_bars,
909 .requested_width = 800, .requested_height = 600 };
910
0/2
✗ Branch 424 → 425 not taken.
✗ Branch 424 → 778 not taken.
✗ cmd.clear(eu::colors::blue_sky, screen);
911
0/2
✗ Branch 425 → 426 not taken.
✗ Branch 425 → 778 not taken.
✗ auto layer = eu::render::with_layer3(cmd, screen);
912 // todo(Gustav): get transform from camera entity
913
0/2
✗ Branch 426 → 427 not taken.
✗ Branch 426 → 778 not taken.
✗ effects.update(dt);
914
0/2
✗ Branch 427 → 428 not taken.
✗ Branch 427 → 778 not taken.
✗ eu::render::Camera camera = camera_fetcher->fetch();
915
0/2
✗ Branch 430 → 431 not taken.
✗ Branch 430 → 776 not taken.
✗ effects.render(eu::render::PostProcArg{
916 .world = &render_world,
917
0/2
✗ Branch 428 → 429 not taken.
✗ Branch 428 → 776 not taken.
✗ .window_size = size_from_v2(layer.screen.get_size()),
918 .final_rect = layer.screen,
919 .camera = &camera,
920 .renderer = &renderer
921 });
922 }
923
924 // render hud
925 {
926 ✗ const auto screen = eu::render::LayoutData{ .style = eu::render::ViewportStyle::black_bars,
927 .requested_width = 1280, .requested_height = 720 };
928
929
0/2
✗ Branch 431 → 432 not taken.
✗ Branch 431 → 786 not taken.
✗ auto layer = eu::render::with_layer2(cmd, screen);
930 ✗ const float hud_size = 100.0f;
931
0/8
✗ Branch 435 → 436 not taken.
✗ Branch 435 → 780 not taken.
✗ Branch 436 → 437 not taken.
✗ Branch 436 → 780 not taken.
✗ Branch 438 → 439 not taken.
✗ Branch 438 → 779 not taken.
✗ Branch 439 → 440 not taken.
✗ Branch 439 → 779 not taken.
✗ eu::render::Quad{ .tint = eu::colors::blue_sky }.draw(layer.batch, layer.viewport_aabb_in_worldspace.get_bottom(hud_size).get_right(hud_size).with_inset(eu::Lrtb{ 5.0f }));
932 ✗ }
933
934 #if FF_HAS(EU_DEBUG_RUNNER)
935
0/2
✗ Branch 441 → 442 not taken.
✗ Branch 441 → 787 not taken.
✗ ImGui::Render();
936
0/4
✗ Branch 442 → 443 not taken.
✗ Branch 442 → 787 not taken.
✗ Branch 443 → 444 not taken.
✗ Branch 443 → 787 not taken.
✗ ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
937 #endif
938
0/2
✗ Branch 444 → 445 not taken.
✗ Branch 444 → 787 not taken.
✗ SDL_GL_SwapWindow(window);
939
940
0/2
✗ Branch 445 → 446 not taken.
✗ Branch 445 → 787 not taken.
✗ runner_world.update(runner::UpdateStage::end_frame, dt);
941 }
942
943 #if FF_HAS(EU_DEBUG_RUNNER)
944
0/2
✗ Branch 448 → 449 not taken.
✗ Branch 448 → 792 not taken.
✗ ImGui_ImplOpenGL3_Shutdown();
945
0/2
✗ Branch 449 → 450 not taken.
✗ Branch 449 → 792 not taken.
✗ ImGui_ImplSDL3_Shutdown();
946
0/2
✗ Branch 450 → 451 not taken.
✗ Branch 450 → 792 not taken.
✗ ImGui::DestroyContext();
947 #endif
948
949
0/6
✗ Branch 451 → 452 not taken.
✗ Branch 451 → 792 not taken.
✗ Branch 452 → 453 not taken.
✗ Branch 452 → 790 not taken.
✗ Branch 453 → 454 not taken.
✗ Branch 453 → 788 not taken.
✗ LOG_INFO("Shutting down");
950
0/2
✗ Branch 455 → 456 not taken.
✗ Branch 455 → 792 not taken.
✗ SDL_GL_DestroyContext(glContext);
951
0/2
✗ Branch 456 → 457 not taken.
✗ Branch 456 → 792 not taken.
✗ SDL_DestroyWindow(window);
952
0/2
✗ Branch 457 → 458 not taken.
✗ Branch 457 → 792 not taken.
✗ SDL_Quit();
953
954 ✗ return 0;
955 ✗ }
956