SanderMertens / flecs

A fast entity component system (ECS) for C & C++
https://www.flecs.dev
Other
6.47k stars 454 forks source link

Crash in world update after restoring snapshot. #620

Closed sixprime closed 4 months ago

sixprime commented 2 years ago

Describe the bug Internal Flecs crash on invalid pointer after trying to update the world following a snapshot restore.

To Reproduce Cf minimal repro source code below :

#include "flecs.h"

struct Player
{
    int id;
};

struct Input
{
    float x;
    float y;
    bool fire;
};

struct Position
{
    float x;
    float y;
};

struct Velocity
{
    float x;
    float y;
};

struct Bullet
{
    int instigator;
};

struct Lifetime
{
    int counter;
};

void ApplyPlayerMovementInput(flecs::iter& it, Velocity* velocity)
{
    flecs::column<const Input> input = it.term<const Input>(2);

    for (auto i : it)
    {
        velocity[i].x = input->x;
        velocity[i].y = input->y;
    }
}

void MovePlayer(flecs::iter& it, const Velocity* velocity, Position* position)
{
    for (auto i : it)
    {
        position[i].x += velocity[i].x;
        position[i].y += velocity[i].y;
    }
}

void ApplyPlayerFireInput(flecs::iter& it, const Player* player, const Velocity* velocity, const Position* position)
{
    flecs::column<const Input> input = it.term<const Input>(4);

    for (auto i : it)
    {
        if (input->fire)
        {
            it.world().entity()
                //.set_name("bullet")
                .set<Bullet>({ player[i].id })
                .set<Position>(position[i])
                .set<Velocity>({ velocity[i].x, velocity[i].y })
                .set<Lifetime>({ 2 });
        }
    }
}

void DestroyBullets(flecs::iter& it, Lifetime* lifetime)
{
    for (auto i : it)
    {
        lifetime[i].counter -= 1;

        if (lifetime[i].counter <= 0)
        {
            it.entity(i).destruct();
        }
    }
}

void MoveBullets(flecs::iter& it, const Velocity* velocity, Position* position)
{
    for (auto i : it)
    {
        position[i].x += velocity[i].x * 6.0f;
        position[i].y += velocity[i].y * 6.0f;
    }
}

int main()
{
    flecs::world world;

    // Initialize world.
    world.set<Input>({ 0.0f, 0.0f, false });

    auto player1 = world.entity("player1")
        .set<Player>({ 1 })
        .set<Position>({ 0.0f, 0.0f })
        .set<Velocity>({ 0.0f, 0.0f });

    world.system<Velocity>("ApplyPlayerMovementInput")
        .term<Input>().singleton()
        .term<Player>()
        .iter(ApplyPlayerMovementInput);

    world.system<const Velocity, Position>("MovePlayer")
        .term<Player>()
        .iter(MovePlayer);

    world.system<const Player, const Velocity, const Position>("ApplyPlayerFireInput")
        .term<Input>().singleton()
        .iter(ApplyPlayerFireInput);

    world.system<Lifetime>("DestroyBullets")
        .iter(DestroyBullets);

    world.system<const Velocity, Position>("MoveBullets")
        .term<Bullet>()
        .iter(MoveBullets);

    // Take initial world state snapshot.
    auto snapshot1 = flecs::snapshot(world);
    snapshot1.take();

    // Update world with input.
    world.set<Input>({ 1.0f, 0.0f, false });
    world.progress();

    world.set<Input>({ 1.0f, 0.0f, true });
    world.progress();

    // Restore initial world state.
    snapshot1.restore();

    // Update world with different input.
    world.set<Input>({ 1.0f, 0.0f, true });
    world.progress(); // <- crash
    // "Exception thrown: read access violation. **column** was 0x30."
    // Crash in flecs.c line 4728 : `int32_t size = column->size;`
    /* Callstack
        >   Game.exe!get_component_w_index(ecs_table_t * table, int column_index, int row) Line 4728    C
        Game.exe!ecs_get_id(const ecs_world_t * world, unsigned __int64 entity, unsigned __int64 id) Line 7634  C
        Game.exe!flecs_defer_set(ecs_world_t * world, ecs_stage_t * stage, ecs_op_kind_t op_kind, unsigned __int64 entity, unsigned __int64 component, int size, const void * value, void * * value_out, bool * is_added) Line 9301 C
        Game.exe!ecs_get_mut_id(ecs_world_t * world, unsigned __int64 entity, unsigned __int64 id, bool * is_added) Line 7705   C
        Game.exe!flecs::set<Bullet,0>(ecs_world_t * world, unsigned __int64 entity, Bullet && value, unsigned __int64 id) Line 11149    C++
        Game.exe!flecs::set<Bullet,Bullet>(ecs_world_t * world, unsigned __int64 entity, Bullet && value) Line 11222    C++
        Game.exe!flecs::entity_builder<flecs::entity>::set<Bullet,0>(Bullet && value) Line 13429    C++
        Game.exe!ApplyPlayerFireInput(flecs::iter & it, const Player * player, const Velocity * velocity, const Position * position) Line 67    C++
        Game.exe!flecs::_::iter_invoker<void (__cdecl*)(flecs::iter &,Player const *,Velocity const *,Position const *),Player const ,Velocity const ,Position const>::invoke_callback<flecs::_::term_ptr,flecs::_::term_ptr,flecs::_::term_ptr,0>(ecs_iter_t * iter, void(*)(flecs::iter &, const Player *, const Velocity *, const Position *) & func, unsigned __int64 __formal, flecs::array<flecs::_::term_ptr,3,void> & __formal, flecs::_::term_ptr <comps_0>, flecs::_::term_ptr <comps_1>, flecs::_::term_ptr <comps_2>) Line 14910    C++
        Game.exe!flecs::_::iter_invoker<void (__cdecl*)(flecs::iter &,Player const *,Velocity const *,Position const *),Player const ,Velocity const ,Position const>::invoke_callback<flecs::_::term_ptr,flecs::_::term_ptr,0>(ecs_iter_t * iter, void(*)(flecs::iter &, const Player *, const Velocity *, const Position *) & func, unsigned __int64 index, flecs::array<flecs::_::term_ptr,3,void> & columns, flecs::_::term_ptr <comps_0>, flecs::_::term_ptr <comps_1>) Line 14923 C++
        Game.exe!flecs::_::iter_invoker<void (__cdecl*)(flecs::iter &,Player const *,Velocity const *,Position const *),Player const ,Velocity const ,Position const>::invoke_callback<flecs::_::term_ptr,0>(ecs_iter_t * iter, void(*)(flecs::iter &, const Player *, const Velocity *, const Position *) & func, unsigned __int64 index, flecs::array<flecs::_::term_ptr,3,void> & columns, flecs::_::term_ptr <comps_0>) Line 14923  C++
        Game.exe!flecs::_::iter_invoker<void (__cdecl*)(flecs::iter &,Player const *,Velocity const *,Position const *),Player const ,Velocity const ,Position const>::invoke_callback<,0>(ecs_iter_t * iter, void(*)(flecs::iter &, const Player *, const Velocity *, const Position *) & func, unsigned __int64 index, flecs::array<flecs::_::term_ptr,3,void> & columns) Line 14923  C++
        Game.exe!flecs::_::iter_invoker<void (__cdecl*)(flecs::iter &,Player const *,Velocity const *,Position const *),Player const ,Velocity const ,Position const>::invoke(ecs_iter_t * iter) Line 14871 C++
        Game.exe!flecs::_::iter_invoker<void (__cdecl*)(flecs::iter &,Player const *,Velocity const *,Position const *),Player const ,Velocity const ,Position const>::run(ecs_iter_t * iter) Line 14878    C++
        Game.exe!ecs_run_intern(ecs_world_t * world, ecs_stage_t * stage, unsigned __int64 system, EcsSystem * system_data, int stage_current, int stage_count, float delta_time, int offset, int limit, const ecs_filter_t * filter, void * param) Line 26400  C
        Game.exe!ecs_pipeline_run(ecs_world_t * world, unsigned __int64 pipeline, float delta_time) Line 25476  C
        Game.exe!ecs_workers_progress(ecs_world_t * world, unsigned __int64 pipeline, float delta_time) Line 24979  C
        Game.exe!ecs_pipeline_run(ecs_world_t * world, unsigned __int64 pipeline, float delta_time) Line 25440  C
        Game.exe!ecs_progress(ecs_world_t * world, float user_delta_time) Line 25665    C
        Game.exe!flecs::world::progress(float delta_time) Line 11325    C++
        Game.exe!main() Line 150    C++
    */

    return 0;
}

Expected behavior I'm expecting the world state to be properly restored and in a state where I can keep using it.

Additional context Flecs version used : 2.6.4. But also crashes with a different callstack on Flecs 3.0.1.

SanderMertens commented 2 years ago

I was able to reduce the scenario to:

struct Foo
{
    int id;
};

int main()
{
    flecs::world world;

    auto snapshot1 = flecs::snapshot(world);

    snapshot1.take();

    world.component<Tag>();

    snapshot1.restore();

    return 0;

    // crashes in v3 on world destruct
}
SanderMertens commented 2 years ago

Another repro:

    ecs_world_t* world = ecs_mini();

    ECS_COMPONENT(world, foobar);

    ecs_entity_t e = ecs_new_id(world);
    ecs_add(world, e, foobar);
    ecs_set(world, e, foobar, { 10 });

    ecs_snapshot_t* snap = ecs_snapshot_take(world);
    ecs_entity_t f = ecs_new_id(world);
    ecs_add(world, f, foobar);
    ecs_set(world, f, foobar, { 11 });
    ecs_snapshot_restore(world, snap);

    ecs_entity_t c = ecs_new_id(world);
    ecs_add(world, c, foobar);
    ecs_set(world, c, foobar, { 11 });

    ecs_fini(world);
SanderMertens commented 4 months ago

Closing issue, as the snapshot feature has been removed from v4.