Cogs.Core
Engine.cpp
1#include "Engine.h"
2
3#include <deque>
4
5#if defined(EMSCRIPTEN) && !defined(COGS_SINGLETHREADED)
6#include <emscripten/threading.h>
7#include <emscripten/proxying.h>
8#endif
9
10#include "Context.h"
11#include "EngineSystems.h"
12#include "EntityStore.h"
13#include "ExtensionRegistry.h"
14#include "ResourceManifest.h"
15
16#include "Editor/IEditor.h"
17
18#include "Platform/Instrumentation.h"
19
20#include "Resources/AnimationManager.h"
21#include "Resources/AssetManager.h"
22#include "Resources/BufferManager.h"
23#include "Resources/BasicBlueNoiseManager.h"
24#include "Resources/EffectManager.h"
25#include "Resources/FontManager.h"
26#include "Resources/MaterialManager.h"
27#include "Resources/MeshManager.h"
28#include "Resources/ModelManager.h"
29#include "Resources/ResourceStore.h"
30#include "Resources/TextureManager.h"
31
32#include "Renderer/IRenderer.h"
33
34#include "Services/Variables.h"
35#include "Services/Time.h"
36#include "Services/QualityService.h"
37#include "Services/ResourceUsageLogger.h"
38
39#include "Systems/Core/TransformSystem.h"
40
41#include "Foundation/Logging/Logger.h"
42
43namespace
44{
45 using namespace Cogs::Core;
46
48
49 struct SystemDefinition
50 {
51 ComponentSystemBase* system;
52 int priority;
53 };
54
55 template<typename ResourceManagerType>
56 ResourceManagerType* createResourceManager(Engine* engine, Context* context)
57 {
58 auto manager = std::make_unique<ResourceManagerType>(context);
59
60 auto ptr = manager.get();
61
62 engine->addResourceManager(std::move(manager));
63
64 return ptr;
65 }
66
67}
68
69namespace Cogs::Core {
70
72 {
73 std::vector<SystemDefinition> systems;
74
75 struct {
76 Cogs::Mutex lock;
77 std::deque<std::function<void()>> tasks;
78 } mainThreadTasks;
79 };
80
81}
82
84 context(context),
85 data(new EngineData())
86{
87#ifdef COGS_SINGLETHREADED
88 workParallelValue = false;
89#else
90 workParallelValue = true;
91#endif
92}
93
95{
96 context->taskManager->waitAll();
97
98 if (editor) {
99 editor->cleanup();
100 editor.reset();
101 }
102
104
105 for (auto & system :data->systems) {
106 system.system->cleanup(context);
107
108 Memory::destroy<ComponentSystemBase>(system.system, context->memory->baseAllocator);
109 }
110
111 data->systems.clear();
112
113 // Disable global update quota to ensure resource managers can cleanup all pending/orphaned resources before
114 // destruction.
115 auto globalQuota = context->variables->get("resources.globalFrameUpdateQuota", 1000);
116 context->variables->set("resources.globalFrameUpdateQuota", 0);
117
118 // Doesn't necessarily exists in unit tests
119 if (context->materialManager) {
120 context->materialManager->releaseAll();
121 }
122 // Do several passes to ensure async dependencies get a chance to propagate.
123 for (int i = 0; i < 4; ++i) {
124 clearResources();
125 context->taskManager->waitAll();
126 }
127
128 //FIXME: Renderer might hold on to regular resources, should be freed before resource cleanup.
129 context->resourceUsageLogger->update(context, true);
130
131 context->bufferManager = nullptr;
132 context->textureManager = nullptr;
133 context->blueNoiseManager = nullptr;
134 context->animationManager = nullptr;
135 context->meshManager = nullptr;
136 context->modelManager = nullptr;
137 context->effectManager = nullptr;
138 context->materialManager = nullptr;
139 context->materialDefinitionManager = nullptr;
140 context->materialInstanceManager = nullptr;
141 context->fontManager = nullptr;
142 context->assetManager = nullptr;
143 resourceManagers.clear();
144
145 context->variables->set("resources.globalFrameUpdateQuota", globalQuota);
146
148 context->resourceUsageLogger->update(context, true);
149}
150
151void Cogs::Core::Engine::runTaskInMainThread(std::function<void()>&& task)
152{
153 {
154 Cogs::LockGuard guard(data->mainThreadTasks.lock);
155 data->mainThreadTasks.tasks.emplace_back(std::move(task));
156 }
157 triggerUpdate();
158}
159
160#if defined(EMSCRIPTEN) && !defined(COGS_SINGLETHREADED)
161
162void Cogs::Core::Engine::invokeCallback(void(*callback)(void*), void* data)
163{
164 static pthread_t mainThread = emscripten_main_runtime_thread_id();
165 if (mainThread == pthread_self()) {
166 callback(data);
167 }
168 else {
169 emscripten_proxy_async(emscripten_proxy_get_system_queue(), mainThread, callback, data);
170 }
171}
172
173namespace {
174
175 // Force alignment of struct to be that of a double so when the payload that
176 // follows this struct contains double it will have the sufficient alignment.
177 struct alignas(8) InvokeComponentNotifyProxyPayload {
178 Context* context;
179 int componentTypeId;
180 size_t entityId;
181 int notification;
182 size_t dataSize;
183 };
184 static_assert((sizeof(InvokeComponentNotifyProxyPayload) % 8) == 0);
185
186 void invokeComponentNotifyProxy(void* payload)
187 {
188 InvokeComponentNotifyProxyPayload* p = reinterpret_cast<InvokeComponentNotifyProxyPayload*>(payload);
189 const void* data = p->dataSize ? ((const char*)p + sizeof(InvokeComponentNotifyProxyPayload)) : nullptr;
190 p->context->callbacks.componentNotifyCallback(p->context, p->componentTypeId, p->entityId, p->notification, data, p->dataSize);
191 free(payload);
192 }
193}
194
195void Cogs::Core::Engine::invokeComponentNotifyCallback(const ComponentModel::Component& component, int notification, const void* data, size_t dataSize)
196{
197 static pthread_t mainThread = emscripten_main_runtime_thread_id();
198 if (context->callbacks.componentNotifyCallback) {
199 if (mainThread == pthread_self()) {
200 context->callbacks.componentNotifyCallback(context, component.getTypeId(), component.getContainer()->getId(), notification, data, dataSize);
201 }
202 else {
203 if (dataSize) { assert(data); }
204
205 // We shall do a async proxy, so we must allocate memory that we can give to the
206 // callback (and the callback must release). We piggy-back a copy of the data
207 // in the same allocation since we the data pointer is only valid for the duration
208 // of this call.
209 char* payload = (char*)malloc(sizeof(InvokeComponentNotifyProxyPayload) + dataSize);
210
211 InvokeComponentNotifyProxyPayload* p = (InvokeComponentNotifyProxyPayload*)payload;
212 *p = InvokeComponentNotifyProxyPayload{ .context = context,
213 .componentTypeId = component.getTypeId(),
214 .entityId = component.getContainer()->getId(),
215 .notification = notification,
216 .dataSize = dataSize
217 };
218
219 if (data) {
220 std::memcpy(payload + sizeof(InvokeComponentNotifyProxyPayload), data, dataSize);
221 }
222
223 emscripten_proxy_async(emscripten_proxy_get_system_queue(), mainThread, invokeComponentNotifyProxy, payload);
224 }
225 }
226}
227
228namespace {
229 struct InvokeResourceLoadProxyPayload {
230 Context* context;
231 int resourceType;
232 ResourceId id;
233 int code;
234 };
235
236 void invokeResourceLoadProxy(void* payload)
237 {
238 InvokeResourceLoadProxyPayload* p = reinterpret_cast<InvokeResourceLoadProxyPayload*>(payload);
239 p->context->callbacks.resourceLoadCallback(p->context, p->resourceType, p->id, p->code);
240 delete p;
241 }
242}
243
244void Cogs::Core::Engine::invokeResourceLoadCallback(int resourceType, ResourceId id, int code)
245{
246 static pthread_t mainThread = emscripten_main_runtime_thread_id();
247 if (context->callbacks.resourceLoadCallback) {
248 if (mainThread == pthread_self()) {
249 context->callbacks.resourceLoadCallback(context, resourceType, id, code);
250 }
251 else {
252 // We shall do a async proxy, so we must allocate memory that we can give to the
253 // callback (and the callback must release).
254 InvokeResourceLoadProxyPayload* payload = new InvokeResourceLoadProxyPayload{
255 .context= context,
256 .resourceType = resourceType,
257 .id = id,
258 .code = code
259 };
260 emscripten_proxy_async(emscripten_proxy_get_system_queue(), mainThread, invokeResourceLoadProxy, payload);
261 }
262 }
263}
264
265#endif
266
267void Cogs::Core::Engine::setDirtyState()
268{
269 lastDirtyFrame = context->time->getFrame();
270 triggerUpdate();
271}
272
273void Cogs::Core::Engine::checkAndUpdateResources()
274{
275 if (ready) return;
276
277 if (firstInit) {
278 resourceManifest = getResourceManifest(context);
279
280 context->resourceStore->preloadResources(resourceManifest);
281
282 firstInit = false;
283 }
284
285 if (!initializing_device && context->resourceStore->hasResources(resourceManifest)) {
286 LOG_INFO(logger, "Found all resources. Running...");
287
288#ifdef EMSCRIPTEN
289 // see comment in Context (ifndef EMSCRIPTEN)
290 context->resourceStore->addResourceArchive(context->variables->get("resources.zipPath", "Cogs.Resources.zip"));
291 context->variables->initialize(*context->resourceStore);
292#endif
293 context->device->initialize();
294 initializing_device = true;
295 }
296
297 if(initializing_device && context->device->isInitializationFinished()){
298 initializing_device = false;
299 LOG_TRACE(logger, "Initializing resource managers...");
300
301 context->bufferManager = createResourceManager<BufferManager>(this, context);
302 context->fontManager = createResourceManager<FontManager>(this, context);
303 context->animationManager = createResourceManager<AnimationManager>(this, context);
304 context->modelManager = createResourceManager<ModelManager>(this, context);
305 context->effectManager = createResourceManager<EffectManager>(this, context);
306 context->materialManager = createResourceManager<MaterialManager>(this, context);
307 context->materialInstanceManager = createResourceManager<MaterialInstanceManager>(this, context);
308 context->meshManager = createResourceManager<MeshManager>(this, context);
309 context->textureManager = createResourceManager<TextureManager>(this, context);
310 context->assetManager = createResourceManager<AssetManager>(this, context);
311 context->blueNoiseManager = createResourceManager<BasicBlueNoiseManager>(this, context);
312
313 context->materialDefinitionManager = context->materialManager->materialDefinitionManager.get();
314
315 initializeResources();
316
317 LOG_TRACE(logger, "Resource managers initialized.");
318
319 context->renderer->initialize(context->device);
320
321 LOG_TRACE(logger, "Initializing engine subsystems...");
322
323 EngineSystems::initialize(context, this);
324
325 LOG_TRACE(logger, "Engine subsystems initialized.");
326
327 Instrumentation::initialize(context);
328 Instrumentation::initializeThread("Main");
329
330 context->initialize();
331
332 // Note: The ready flag below is (among other things) used to signal that extensions
333 // registered also must run system initialization when registering as the engine is
334 // already initialized. That is, be careful of adding code between the loop below and
335 // setting ready to true.
336 for (SystemDefinition& system : data->systems) {
337 system.system->initialize(context);
338 }
339 ready = true;
340 }
341
342 if (!ready) {
343 static bool loggedDelay = false;
344 if (!loggedDelay) {
345 LOG_INFO(logger, "Delaying initialization for resource load...");
346 loggedDelay = true;
347 }
348 }
349}
350
351void Cogs::Core::Engine::setEditor(IEditor * editor) { this->editor.reset(editor); }
352
353void Cogs::Core::Engine::addResourceManager(std::unique_ptr<IResourceManager> && resourceManager)
354{
355 resourceManagers.push_back(std::move(resourceManager));
356}
357
359{
360 CpuInstrumentationScope(SCOPE_ENGINE, "Engine::update");
361 OsoMP_Message("Engine::update", 0xFFFF9911, nullptr, 0);
362
363 context->qualityService->beginFrame();
364
365 workParallelValue = context->variables->get("engine.workParallel", workParallelValue);
366
367 // Process tasks that have arrived since last frame
368 bool tasksDone = true;
369 Cogs::Timer taskTimer = Cogs::Timer::startNew();
370 const int mainThreadTasksFrameLimitMs = context->variables->get("engine.mainThreadTasksFrameLimitMs", 5);
371 while(true) {
372
373 // Try to pull a task
374 std::function<void()> f;
375 {
376 Cogs::LockGuard guard(data->mainThreadTasks.lock);
377 if (data->mainThreadTasks.tasks.empty()) break;
378 f = std::move(data->mainThreadTasks.tasks.front());
379 data->mainThreadTasks.tasks.pop_front();
380 }
381 // Break out if there are no more tasks to run.
382 if (!f) {
383 break;
384 }
385 // Run task
386 f();
387 // To avoid stuttering, we limit the amount of tasks we do per frame.
388 // Test is after running the task, so we will always run at least one task and have progress.
389 if ((0 < mainThreadTasksFrameLimitMs) && (mainThreadTasksFrameLimitMs < taskTimer.elapsedMilliseconds())) {
390 Cogs::LockGuard guard(data->mainThreadTasks.lock);
391 tasksDone = data->mainThreadTasks.tasks.empty();
392 break;
393 }
394 }
395
396 if (!ready) {
397 checkAndUpdateResources();
398 if (!ready) return;
399 }
400
401 updateRequested = false;
402 if (!tasksDone) {
403 setDirty(); // We're not done with the main thread task request another frame
404 }
405
406 if (editor) {
407 editor->beginFrame();
408 }
409
410 bool vsync = context->variables->get("renderer.vSync", false);
411 bool rendererDisable = context->variables->get("renderer.disable", false);
412
413 if (!rendererDisable) {
414 CpuInstrumentationScope(SCOPE_RENDERING, "Engine::beginFrame");
415 context->renderer->beginFrame();
416 }
417
418 context->update();
419 updateSystems();
420
421 if (!rendererDisable) {
422 {
423 CpuInstrumentationScope(SCOPE_RENDERING, "Engine::render");
424 context->qualityService->begin(MetricType::PreRender);
425 preRender();
426 context->qualityService->end(MetricType::PreRender);
427 render();
428 postRender();
429 }
430 {
431 CpuInstrumentationScope(SCOPE_RENDERING, "Engine::present");
432 context->qualityService->begin(MetricType::Present);
433 context->renderer->endFrame(vsync ? 1 : 0);
434 context->qualityService->end(MetricType::Present);
435 }
436 }
437
438 context->resourceUsageLogger->update(context);
439 context->qualityService->endFrame();
440}
441
443{
444 if (systemFlags & SystemFlags::Changed) {
445 std::sort(data->systems.begin(), data->systems.end(), [&](const SystemDefinition & left, const SystemDefinition & right)
446 {
447 return left.priority < right.priority;
448 });
449
450 systemFlags &= ~SystemFlags::Changed;
451 }
452
453 static thread_local std::vector<SystemDefinition> localSystems;
454 // Make a local copy of the systems array in case it is modified during the update
455 // phase. This ensures we only iterate over systems once in a given frame, and that the
456 // data is not invalidated while iterating.
457 localSystems = data->systems;
458
459 context->transformSystem->handleOriginUpdate(context);
460 {
461 CpuInstrumentationScope(SCOPE_ENGINE, "Engine::preUpdateSystems");
462 for (auto & system : localSystems) {
463 system.system->instrumentedPreUpdate();
464 }
465 }
466
467 {
468 CpuInstrumentationScope(SCOPE_ENGINE, "Engine::updateSystems");
469 for (auto & system : localSystems) {
470 system.system->instrumentedUpdate();
471 }
472 }
473
474 if (context->callbacks.postSystemsUpdateCallback) {
475 context->callbacks.postSystemsUpdateCallback(context);
476 }
477
478 {
479 CpuInstrumentationScope(SCOPE_ENGINE, "Engine::postUpdateSystems");
480 for (auto & system : localSystems) {
481 system.system->instrumentedPostUpdate();
482 }
483 }
484}
485
486void Cogs::Core::Engine::preRender()
487{
488 CpuInstrumentationScope(SCOPE_ENGINE, "PreRender");
489
490 context->preRender();
491
492 for (auto & resourceManager : resourceManagers) {
493 resourceManager->processLoading();
494 }
495
496 {
497 CpuInstrumentationScope(SCOPE_ENGINE, "processSwapping");
498
499 for (auto & resourceManager : resourceManagers) {
500 resourceManager->processSwapping();
501 }
502 }
503
504 {
505 CpuInstrumentationScope(SCOPE_ENGINE, "activateResources");
506
507 for (auto & resourceManager : resourceManagers) {
508 resourceManager->activateResources();
509 }
510 }
511
512 {
513 CpuInstrumentationScope(SCOPE_ENGINE, "processDeletion");
514
515 for (auto & resourceManager : resourceManagers) {
516 resourceManager->processDeletion();
517 }
518 }
519}
520
521void Cogs::Core::Engine::swapResources()
522{
523 CpuInstrumentationScope(SCOPE_ENGINE, "SwapResources");
524
525 for (auto & resourceManager : resourceManagers) {
526 resourceManager->processSwapping();
527 }
528}
529
530
531void Cogs::Core::Engine::render()
532{
533 CpuInstrumentationScope(SCOPE_ENGINE, "Render");
534
535 context->renderer->render();
536}
537
538void Cogs::Core::Engine::postRender()
539{
540
541}
542
543void Cogs::Core::Engine::registerSystem(ComponentSystemBase * system, int priority, bool registerInStore)
544{
545 SystemDefinition definition = { system, priority };
546
547 if (registerInStore) {
548 context->store->addSystem(system);
549 }
550
551 data->systems.push_back(definition);
552
553 systemFlags |= SystemFlags::Changed;
554}
555
557{
558 for (auto & resourceManager : resourceManagers) {
559 resourceManager->initialize();
560 }
561}
562
564{
565 for (auto & resourceManager : resourceManagers) {
566 resourceManager->clearSwapping();
567 }
568
569 for (auto & resourceManager : resourceManagers) {
570 resourceManager->clear();
571 }
572 for (auto & resourceManager : resourceManagers) {
573 resourceManager->processDeletion();
574 }
575}
576
577Cogs::Core::IValueTypeManager* Cogs::Core::Engine::getResourceManagerByValueType(int valueType)
578{
579 auto valueType_ = (Cogs::Core::DefaultValueType)valueType;
580 for (auto & mgt : resourceManagers) {
581 auto * ptr = dynamic_cast<IValueTypeManager*>(mgt.get());
582 if (ptr && ptr->doesManage(valueType_)) return ptr;
583 }
584 return nullptr;
585}
Base class for Component instances.
Definition: Component.h:143
class Entity * getContainer() const
Get the container currently owning this component instance.
Definition: Component.h:151
constexpr Reflection::TypeId getTypeId() const
Get the Reflection::TypeId of the component.
Definition: Component.h:380
constexpr size_t getId() const noexcept
Get the unique identifier of this entity.
Definition: Entity.h:113
Base class for component systems.
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
class IRenderer * renderer
Renderer.
Definition: Context.h:228
void initialize()
Initialize all services and component systems.
Definition: Context.cpp:304
void clear()
Does clearing of context.
Definition: Context.cpp:355
std::unique_ptr< class QualityService > qualityService
Quality service instance.
Definition: Context.h:201
class EntityStore * store
Entity store.
Definition: Context.h:231
std::unique_ptr< class ResourceUsageLogger > resourceUsageLogger
Resource usage logger service instance.
Definition: Context.h:183
std::unique_ptr< struct MemoryContext > memory
Memory and allocation info.
Definition: Context.h:171
std::unique_ptr< class TaskManager > taskManager
TaskManager service instance.
Definition: Context.h:186
std::unique_ptr< class Variables > variables
Variables service instance.
Definition: Context.h:180
std::unique_ptr< class Time > time
Time service instance.
Definition: Context.h:198
std::unique_ptr< class ResourceStore > resourceStore
ResourceStore service instance.
Definition: Context.h:210
The engine owns all the systems and resource managers, and is responsible for executing different sta...
Definition: Engine.h:88
~Engine()
Destructs the engine instance.
Definition: Engine.cpp:94
void invokeResourceLoadCallback(int resourceType, ResourceId id, int code)
Invoke the resourceLoadCallback if it exists, proxied from emscripten worker threads.
Definition: Engine.h:170
void updateSystems()
Performs updates of all the systems.
Definition: Engine.cpp:442
void update()
Update the engine, advancing the system state a single frame.
Definition: Engine.cpp:358
void clearResources()
Clears all resource managers pending reinitialization of resources.
Definition: Engine.cpp:563
void invokeCallback(void(*callback)(void *), void *data)
Generic callback invoke wrapper for callbacks with just a data-wrapper, proxied from emscripten worke...
Definition: Engine.h:160
void addResourceManager(std::unique_ptr< IResourceManager > &&resourceManager)
Add the given resource manager to the engine.
Definition: Engine.cpp:353
void initializeResources()
Initializes all resource managers and default resources.
Definition: Engine.cpp:556
Engine(Context *context)
Constructs a new engine instance, living in the given Context.
Definition: Engine.cpp:83
void registerSystem(ComponentSystemBase *system, int priority, bool registerInStore=true)
Register the given component system in the engine with the given priority.
Definition: Engine.cpp:543
void invokeComponentNotifyCallback(const ComponentModel::Component &component, int notification, const void *data, size_t dataSize)
Invoke the componentNotifyCallback if it exists, proxied from emscripten worker threads.
Definition: Engine.h:163
void addSystem(const Reflection::TypeId typeId, ComponentCreator creator, ComponentDestroyer destroyer)
Adds the given creator and destroyer functions for handling components of the given typeId.
static void cleanup(Context *context)
Cleanup the given context.
static void remove(Context *context)
Removes the given context.
virtual void endFrame(uint32_t syncInterval=0, uint32_t presentFlags=Cogs::PresentFlags::None)=0
Signals the end of the current frame.
virtual void beginFrame()=0
Signals the beginning of a new frame.
virtual void render()=0
Kick off the actual rendering, allowing the renderer to produce its output.
Log implementation class.
Definition: LogManager.h:139
Old timer class.
Definition: Timer.h:37
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
DefaultValueType
Defines value types for default values.
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:180