Closed teksoc-1 closed 4 years ago
Interesting idea- if I were to add a function to the framework that returns true / false depending on whether any systems are active, would that allow you to build the functionality you mentioned?
This way, your asynchronous thread could periodically check this function, and depending on whether the value is true or false, you could unblock the main thread by signaling a condition variable or equivalent.
@SanderMertens Conceptually this is the kind of runtime would like to achieve :
ecs_main_run_loop()
{
should_exit = false
do
{
if ( ecs_progress() )
{
if ( !ecs_systems_active() )
{
// Asynchronously start all event based systems
ecs_run_event_based_systems()
// Blocking wait
ecs_wait_for_event()
// Event fired ... continue to cycle through ecs_progress() as long as systems are active
}
}
else
{
should_exit = true
}
} while ( !should_exit )
}
The goal is to use ECS design architecture, yet be able to throttle back when work is no longer being performed, until some external event is fired that introduces change into the environment. There's obviously some concurrency and synchronization issues (staging?) to handle when events are being fired during the ecs_progress() phased loop. Hopefully this provides some better clarity on the intent behind this feature request.
@teksoc-1 makes sense, this is consistent with what I understood from your initial description. The pattern looks good, but it's also quite specific so I would rather build functions that enable this vs. hardcoding it.
If I can give you a ecs_systems_active
function, I think that will let you do everything you need. Your event-based systems can use the regular ECS API (provided that you ensure thread-safe access to the world) and at some point in time, the ecs_systems_active()
function will return true again, because you performed an action that activated one or more systems.
In the same thread(s) as where you run your event based systems you could run ecs_systems_active()
and when it returns true, signal a condition variable that you're waiting for in the main loop. Something like:
struct ctx {
ecs_world_t *world;
cond_t cond_var;
}
void* run_event_systems(ctx* arg) {
do {
// run event based systems
} while ( !ecs_systems_active(world) ) ;
cond_signal(arg->cond_var);
}
void main_loop() {
should_exit = false
do
{
if ( ecs_progress(world, 0) )
{
if ( !ecs_systems_active(world) )
{
// Asynchronously start all event based systems
thread_start(run_event_systems, {world, cond_var});
// Blocking wait
cond_wait(cond_var);
// Event fired ... continue to cycle through ecs_progress() as long as systems are active
}
}
else
{
should_exit = true
}
} while ( !should_exit )
}
@SanderMertens Concur on the enabling functions, vice hard coding, and I believe the ecs_systems_active
- like api is the core primitive, building block.
@SanderMertens So one way I was achieving execution of event based systems was to add an EventBased component to manual systems (since systems are first class citizens in the sense they are entities and can have components added to them as well), this functionality seems to however have broken with the recent updates after pulling them in. Adding components to a system seems to longer have an effect. System entities containing components are no longer matched against. Is there a better way I should perhaps be doing this in the first place?
@teksoc-1 It should still be possible to add components to systems, but something may have broken with the SYSTEM
modifier (although my tests are not showing it).
Can you provide a bit more detail about the issue?
@SanderMertens I was not using the SYSTEM
modifier, but rather explicitly adding/setting components on the systems just like any entity - it was working before. This was my way of registering Event Based Systems - seemed natural to me at least - a manual system that fired up the async Event Based Systems. Below is a sample test case.
#include "flecs.h"
#define TRIGGER_ERROR 1
#define VERIFY_FIX 0
typedef int MyComponent;
int MyManualSystem1Ran = 0;
int MyManualSystem2Ran = 0;
void
MyManualSystem1( ecs_rows_t * rows )
{
MyManualSystem1Ran = 1;
}
void
MyManualSystem2( ecs_rows_t * rows )
{
MyManualSystem2Ran = 1;
for ( unsigned int i = 0; i < rows->count; i++ )
{
#if VERIFY_FIX
ecs_run( rows->world, rows->entities[ i ], 0, NULL );
#endif
}
}
int main( int argc, char ** argv )
{
ecs_world_t * world = ecs_init();
ecs_entity_t testEntity = ECS_INVALID_ENTITY;
// Register components
ECS_COMPONENT( world, MyComponent );
// Register systems
ECS_SYSTEM( world, MyManualSystem1, EcsManual, 0 );
// Note: MyManualSystem2 registers interest in entities containting the MyComponent component
ECS_SYSTEM( world, MyManualSystem2, EcsManual, MyComponent );
#if TRIGGER_ERROR
testEntity = MyManualSystem1;
#else
testEntity = ecs_new( world, 0 );
#endif
// Add MyComponent component to an entity
ecs_add( world, testEntity, MyComponent );
// Verify this entity has the MyComponent component
ecs_assert( ecs_has( world, testEntity, MyComponent), ECS_INTERNAL_ERROR, NULL );
// Run the manual system that matches against entities with a MyComponent
ecs_run( world, MyManualSystem2, 0, NULL );
// if TRIGGER_ERROR, fails here
ecs_assert( 0 != MyManualSystem2Ran, ECS_INTERNAL_ERROR, NULL );
#if VERIFY_FIX
// When fix applied, this should work
ecs_assert( 0 != MyManualSystem1Ran, ECS_INTERNAL_ERROR, NULL );
#endif
return ecs_fini( world) ;
}
Found the issue, it was introduced by the fix for #160 which caused systems to always be evaluated with a filter, even if none is provided - it would use 0 for include type.
When a filter without a type is provided, builtin entities are automatically filtered out, so that an application can (for example) do a bulk delete of all entities without accidentally deleting the entities for components / systems. In this case, that caused the 0-filter to filter out the system, which is why the manual system wasn't being invoked.
Fixed the issue by making sure that when using ecs_run
no filter is used. If you're still seeing issues, feel free to reopen the issue.
@SanderMertens Awesome, thanks - works here.
@SanderMertens Incorporated the ecs_active_system_count()
- this was a nice addition, and is working well. I think the ecs_merge_world()
from #122 will be a nice complementary api to this event based design approach as well.
Describe the problem you are trying to solve. @SanderMertens The
while ( ecs_progress() )
construct works well when you continuously are wanting to perform work (i.e. animation/simulation), however in the event that an iteration does not match any systems, subsequent calls toecs_progress
will not (ever) introduce any changes into the environment (unless you introduce a change outside of theecs_progress()
pipeline). Conceptually the goal is to leverage theecs_progress()
pipeline as long as work is being performed (systems matched), and throttle back when systems are no longer matched, yet provide a way for N Event Based Systems to asynchronously fire an event and trigger theecs_progresss()
pipeline again (which may introduce new Event Based Systems).Describe the solution you'd like Looking for a solution that will spin
ecs_progess()
until work is no longer being performed (no systems matched), orecs_quit()
called ... and then perhaps anecs_wait_for_event()
like call that blocks until some Event Based System that is asynchronously running fires an event (new entity for example) and subsequently spinsecs_progress()
as long as work is being performed. This of course requires some sort of registration of Event Based Systems that can be run asynchronously outside of the periodicecs_progress()
pipeline.