diff --git a/headers/enduro2d/high/_all.hpp b/headers/enduro2d/high/_all.hpp index aac67fd5..0df2fd6c 100644 --- a/headers/enduro2d/high/_all.hpp +++ b/headers/enduro2d/high/_all.hpp @@ -36,6 +36,7 @@ #include "components/renderer.hpp" #include "components/scene.hpp" #include "components/spine_player.hpp" +#include "components/spine_animation_event.hpp" #include "components/sprite_renderer.hpp" #include "systems/flipbook_system.hpp" diff --git a/headers/enduro2d/high/components/spine_animation_event.hpp b/headers/enduro2d/high/components/spine_animation_event.hpp new file mode 100644 index 00000000..12e39b1c --- /dev/null +++ b/headers/enduro2d/high/components/spine_animation_event.hpp @@ -0,0 +1,70 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018-2019, by Matvey Cherevko (blackmatov@gmail.com) + ******************************************************************************/ + +#pragma once + +#include "../_high.hpp" + +#include "../factory.hpp" + +namespace e2d +{ + class spine_animation_event final { + public: + struct clear_track { + u32 track; + }; + + struct set_anim { + u32 track; + str name; + bool loop; + str on_complete; + }; + + struct add_anim { + u32 track; + str name; + bool loop; + secf delay; + str on_complete; + }; + + struct empty_anim { + u32 track; + secf duration; + secf delay; + str on_complete; + }; + + using commands_t = stdex::variant< + clear_track, + set_anim, + add_anim, + empty_anim>; + public: + spine_animation_event() = default; + + std::vector& commands() noexcept { return commands_; } + const std::vector& commands() const noexcept { return commands_; } + private: + std::vector commands_; + }; + + template <> + class factory_loader final : factory_loader<> { + public: + static const char* schema_source; + + bool operator()( + spine_animation_event& component, + const fill_context& ctx) const; + + bool operator()( + asset_dependencies& dependencies, + const collect_context& ctx) const; + }; +} diff --git a/headers/enduro2d/high/components/spine_player.hpp b/headers/enduro2d/high/components/spine_player.hpp index bf7ae847..25e40190 100644 --- a/headers/enduro2d/high/components/spine_player.hpp +++ b/headers/enduro2d/high/components/spine_player.hpp @@ -15,11 +15,23 @@ struct spAnimationState; struct spSkeleton; struct spSkeletonClipping; struct spVertexEffect; +struct spTrackEntry; +struct spEvent; namespace e2d { class spine_player final { public: + class on_complete_event final { + public: + on_complete_event() = default; + + std::vector& completed() noexcept { return completed_; } + const std::vector& completed() const noexcept { return completed_; } + private: + std::vector completed_; + }; + using animation_ptr = std::shared_ptr; using skeleton_ptr = std::shared_ptr; using clipping_ptr = std::shared_ptr; @@ -37,27 +49,15 @@ namespace e2d [[nodiscard]] bool has_animation(const str& name) const noexcept; - spine_player& set_animation(u32 track, const str& name, bool loop = false) noexcept; - - spine_player& add_animation(u32 track, const str& name, bool loop, secf delay = secf(0.0f)) noexcept; - spine_player& add_animation(u32 track, const str& name, secf delay = secf(0.0f)) noexcept; - - spine_player& add_empty_animation(u32 track, secf duration, secf delay = secf(0.0f)) noexcept; - - spine_player& clear(u32 track) noexcept; - spine_player& clear() noexcept; - const animation_ptr& animation() const noexcept; const skeleton_ptr& skeleton() const noexcept; const clipping_ptr& clipper() const noexcept; - const effect_ptr& effect() const noexcept; const spine_model_asset::ptr& model() const noexcept; private: animation_ptr animation_; spine_model_asset::ptr model_; skeleton_ptr skeleton_; clipping_ptr clipping_; - effect_ptr effect_; }; template <> diff --git a/headers/enduro2d/high/systems/spine_system.hpp b/headers/enduro2d/high/systems/spine_system.hpp index eee98595..0bdefa68 100644 --- a/headers/enduro2d/high/systems/spine_system.hpp +++ b/headers/enduro2d/high/systems/spine_system.hpp @@ -15,5 +15,8 @@ namespace e2d spine_system(); ~spine_system() noexcept final; void process(ecs::registry& owner) override; + private: + class internal_state; + std::unique_ptr state_; }; } diff --git a/samples/bin/library/scene_spine_prefab.json b/samples/bin/library/scene_spine_prefab.json new file mode 100644 index 00000000..7808c5ae --- /dev/null +++ b/samples/bin/library/scene_spine_prefab.json @@ -0,0 +1,24 @@ +{ + "components" : { + "scene" : {} + }, + "children" : [{ + "prototype" : "camera_prefab.json" + },{ + "prototype" : "spine_coin_prefab.json", + "components" : { + "actor" : { + "translation" : [200,180,0], + "scale" : 0.25 + } + } + }, { + "prototype" : "spine_raptor_prefab.json", + "components" : { + "actor" : { + "translation" : [-80,-100,0], + "scale" : 0.25 + } + } + }] +} diff --git a/samples/bin/library/spine_coin_prefab.json b/samples/bin/library/spine_coin_prefab.json new file mode 100644 index 00000000..e3ef44fb --- /dev/null +++ b/samples/bin/library/spine_coin_prefab.json @@ -0,0 +1,17 @@ +{ + "components" : { + "renderer" : { + "materials" : ["spine_material.json"] + }, + "spine_player" : { + "model" : "spine_coin.json" + }, + "spine_animation" : { + "animations" : [{ + "track" : 0, + "name" : "animation", + "loop" : true + }] + } + } +} diff --git a/samples/bin/library/spine_raptor_prefab.json b/samples/bin/library/spine_raptor_prefab.json new file mode 100644 index 00000000..e7d57053 --- /dev/null +++ b/samples/bin/library/spine_raptor_prefab.json @@ -0,0 +1,17 @@ +{ + "components" : { + "renderer" : { + "materials" : ["spine_material.json"] + }, + "spine_player" : { + "model" : "spine_raptor.json" + }, + "spine_animation" : { + "animations" : [{ + "track" : 0, + "name" : "walk", + "loop" : true + }] + } + } +} diff --git a/samples/sources/sample_07/sample_07.cpp b/samples/sources/sample_07/sample_07.cpp index 4aeb8bc6..e82a80e1 100644 --- a/samples/sources/sample_07/sample_07.cpp +++ b/samples/sources/sample_07/sample_07.cpp @@ -11,11 +11,6 @@ namespace { class game_system final : public ecs::system { public: - game_system(gobject_iptr raptor) - : raptor_gobj_(raptor) {} - - ~game_system() noexcept override {} - void process(ecs::registry& owner) override { E2D_UNUSED(owner); const keyboard& k = the().keyboard(); @@ -33,33 +28,39 @@ namespace } // use keys R, J, G to start animations - if ( raptor_gobj_ ) { - if ( k.is_key_just_pressed(keyboard_key::r) ) { - auto player = raptor_gobj_->get_component(); - if ( player ) { - (*player).set_animation(0, "roar") - .add_animation(0, "walk", true); - } - } - if ( k.is_key_just_pressed(keyboard_key::j) ) { - auto player = raptor_gobj_->get_component(); - if ( player ) { - (*player).set_animation(0, "jump") - .add_animation(0, "walk", true); - } - } - if ( k.is_key_just_pressed(keyboard_key::g) ) { - auto player = raptor_gobj_->get_component(); - if ( player ) { - (*player).set_animation(1, "gun-grab") - .add_animation(1, "gun-holster", secf(3.0f)); - } - } - } - } + const bool roar = k.is_key_just_pressed(keyboard_key::r); + const bool jump = k.is_key_just_pressed(keyboard_key::j); + const bool gun_grab = k.is_key_just_pressed(keyboard_key::g); - private: - gobject_iptr raptor_gobj_; + if ( roar || jump || gun_grab ) { + owner.for_each_component( + [&owner, roar, jump, gun_grab] (ecs::entity_id e, const spine_player& player) { + if ( !player.has_animation("walk") ) { + return; + } + auto& events = ecs::entity(owner, e).assign_component(); + if ( roar ) { + events.commands().push_back(spine_animation_event::set_anim{0, "roar", false, "1"}); + } else if ( jump ) { + events.commands().push_back(spine_animation_event::set_anim{0, "jump", false, "1"}); + } else if ( gun_grab ) { + events.commands().push_back(spine_animation_event::set_anim{1, "gun-grab", false, ""}); + events.commands().push_back(spine_animation_event::add_anim{1, "gun-holster", false, secf(3.0f), ""}); + } + }); + } + + owner.for_joined_components( + [&owner](ecs::entity_id e, const spine_player::on_complete_event& events, spine_player& player) { + auto& new_events = ecs::entity(owner, e).assign_component(); + + for ( auto& ev : events.completed() ) { + if ( ev == "1" ) { + new_events.commands().push_back(spine_animation_event::add_anim{0, "walk", true, secf(), ""}); + } + } + }); + } }; class camera_system final : public ecs::system { @@ -86,47 +87,11 @@ namespace } private: bool create_scene() { - auto spine_raptor = the().load_asset("spine_raptor.json"); - auto spine_coin = the().load_asset("spine_coin.json"); - auto spine_mat = the().load_asset("spine_material.json"); - - if ( !spine_raptor || !spine_coin || !spine_mat ) { - return false; - } - - auto scene_i = the().instantiate(); - - scene_i->entity_filler() - .component() - .component(node::create(scene_i)); - - node_iptr scene_r = scene_i->get_component().get().node(); - - auto coin_i = the().instantiate(); - coin_i->entity_filler() - .component(node::create(coin_i, scene_r)) - .component(renderer() - .materials({spine_mat})) - .component(spine_player(spine_coin) - .set_animation(0, "animation", true)); - - node_iptr coin_n = coin_i->get_component().get().node(); - coin_n->scale(v3f(0.25f)); - coin_n->translation(v3f{200.0f, 180.0f, 0.0f}); - - raptor_gobj_ = the().instantiate(); - raptor_gobj_->entity_filler() - .component(node::create(raptor_gobj_, scene_r)) - .component(renderer() - .materials({spine_mat})) - .component(spine_player(spine_raptor) - .set_animation(0, "walk", true)); - - node_iptr raptor_n = raptor_gobj_->get_component().get().node(); - raptor_n->scale(v3f(0.25f)); - raptor_n->translation(v3f{-80.f, -100.f, 0.0f}); - - return true; + auto scene_prefab_res = the().load_asset("scene_spine_prefab.json"); + auto scene_go = scene_prefab_res + ? the().instantiate(scene_prefab_res->content()) + : nullptr; + return !!scene_go; } bool create_camera() { @@ -140,13 +105,10 @@ namespace bool create_systems() { ecs::registry_filler(the().registry()) - .system(world::priority_update, raptor_gobj_) + .system(world::priority_update) .system(world::priority_pre_render); return true; } - - private: - gobject_iptr raptor_gobj_; }; } diff --git a/sources/enduro2d/high/components/spine_animation_event.cpp b/sources/enduro2d/high/components/spine_animation_event.cpp new file mode 100644 index 00000000..958054f7 --- /dev/null +++ b/sources/enduro2d/high/components/spine_animation_event.cpp @@ -0,0 +1,94 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018-2019, by Matvey Cherevko (blackmatov@gmail.com) + ******************************************************************************/ + +#include + +namespace e2d +{ + const char* factory_loader::schema_source = R"json({ + "type" : "object", + "required" : [], + "additionalProperties" : false, + "properties" : { + "animations" : { + "type" : "array", + "items" : { "$ref": "#/definitions/spine_animation" } + } + }, + "definitions" : { + "spine_animation" : { + "type" : "object", + "required" : [ "track", "name" ], + "additionalProperties" : false, + "properties" : { + "track" : { "type" : "integer", "minimum" : 0, "maximum": 8 }, + "name" : { "$ref": "#/common_definitions/name" }, + "loop" : { "type" : "boolean" }, + "delay" : { "type" : "number" } + } + } + } + })json"; + + bool factory_loader::operator()( + spine_animation_event& component, + const fill_context& ctx) const + { + if ( ctx.root.HasMember("animations") ) { + const auto& animations_json = ctx.root["animations"]; + E2D_ASSERT(animations_json.IsArray()); + + for ( rapidjson::SizeType i = 0; i < animations_json.Size(); ++i ) { + E2D_ASSERT(animations_json[i].IsObject()); + const auto& item_json = animations_json[i]; + + E2D_ASSERT(item_json.HasMember("track") && item_json.HasMember("name")); + u32 track = 0; + secf delay; + bool loop = false; + const char* name = ""; + + if ( item_json.HasMember("name") ) { + name = item_json["name"].GetString(); + } + + if ( item_json.HasMember("track") ) { + if ( !json_utils::try_parse_value(item_json["track"], track) ) { + the().error("SPINE_ANIMATION: Incorrect formatting of 'animations.track' property"); + return false; + } + } + + if ( item_json.HasMember("delay") ) { + if ( !json_utils::try_parse_value(item_json["delay"], delay.value) ) { + the().error("SPINE_ANIMATION: Incorrect formatting of 'animations.delay' property"); + return false; + } + } + + if ( item_json.HasMember("loop") ) { + if ( !json_utils::try_parse_value(item_json["loop"], loop) ) { + the().error("SPINE_ANIMATION: Incorrect formatting of 'animations.loop' property"); + return false; + } + } + + component.commands().emplace_back(spine_animation_event::add_anim{ + track, name, loop, delay, ""}); + } + } + + return true; + } + + bool factory_loader::operator()( + asset_dependencies& dependencies, + const collect_context& ctx) const + { + E2D_UNUSED(dependencies, ctx); + return true; + } +} diff --git a/sources/enduro2d/high/components/spine_player.cpp b/sources/enduro2d/high/components/spine_player.cpp index 336d48af..a1c94647 100644 --- a/sources/enduro2d/high/components/spine_player.cpp +++ b/sources/enduro2d/high/components/spine_player.cpp @@ -7,12 +7,6 @@ #include #include -namespace -{ - // spine will allocate storage for each track, so keep number of track as small as possible - constexpr e2d::u32 max_track_count = 8; -} - namespace e2d { spine_player::spine_player(const spine_model_asset::ptr& model) { @@ -32,6 +26,7 @@ namespace e2d animation_ = animation_ptr( spAnimationState_create(model_->content().animation().get()), spAnimationState_dispose); + E2D_ASSERT(animation_); // TODO exception? } else { animation_.reset(); } @@ -42,7 +37,7 @@ namespace e2d spSkeleton_setSkinByName(skeleton_.get(), value.empty() ? nullptr : value.c_str()); return *this; } - + spine_player& spine_player::attachment(const str& slot, const str& name) noexcept { E2D_ASSERT(!slot.empty()); E2D_ASSERT(!name.empty()); @@ -51,7 +46,7 @@ namespace e2d } return *this; } - + const spine_player::skeleton_ptr& spine_player::skeleton() const noexcept { return skeleton_; } @@ -60,10 +55,6 @@ namespace e2d return clipping_; } - const spine_player::effect_ptr& spine_player::effect() const noexcept { - return effect_; - } - spine_player& spine_player::time_scale(float value) noexcept { E2D_ASSERT(animation_); animation_->timeScale = value; @@ -80,65 +71,11 @@ namespace e2d return false; } spAnimation* anim = spSkeletonData_findAnimation( - model_->content().skeleton().operator->(), + model_->content().skeleton().get(), name.c_str()); return anim != nullptr; } - spine_player& spine_player::set_animation(u32 track, const str& name, bool loop) noexcept { - E2D_ASSERT(model_ && animation_); - E2D_ASSERT(track < max_track_count); - spAnimation* anim = spSkeletonData_findAnimation( - model_->content().skeleton().operator->(), - name.c_str()); - - if ( !anim ) { - the().error("SPINE_PLAYER: animation '%0' is not found", name); - return *this; - } - spAnimationState_setAnimation(animation_.get(), track, anim, loop); - return *this; - } - - spine_player& spine_player::add_animation(u32 track, const str& name, secf delay) noexcept { - return add_animation(track, name, false, delay); - } - - spine_player& spine_player::add_animation(u32 track, const str& name, bool loop, secf delay) noexcept { - E2D_ASSERT(model_ && animation_); - E2D_ASSERT(track < max_track_count); - spAnimation* anim = spSkeletonData_findAnimation( - model_->content().skeleton().operator->(), - name.c_str()); - - if ( !anim ) { - the().error("SPINE_PLAYER: animation '%0' is not found", name); - return *this; - } - spAnimationState_addAnimation(animation_.get(), track, anim, loop, delay.value); - return *this; - } - - spine_player& spine_player::add_empty_animation(u32 track, secf duration, secf delay) noexcept { - E2D_ASSERT(animation_); - E2D_ASSERT(track < max_track_count); - spAnimationState_addEmptyAnimation(animation_.get(), track, duration.value, delay.value); - return *this; - } - - spine_player& spine_player::clear(u32 track) noexcept { - E2D_ASSERT(animation_); - E2D_ASSERT(track < max_track_count); - spAnimationState_clearTrack(animation_.get(), track); - return *this; - } - - spine_player& spine_player::clear() noexcept { - E2D_ASSERT(animation_); - spAnimationState_clearTracks(animation_.get()); - return *this; - } - const spine_player::animation_ptr& spine_player::animation() const noexcept { return animation_; } @@ -160,24 +97,9 @@ namespace e2d "attachments" : { "type" : "array", "items" : { "$ref": "#/definitions/spine_attachment" } - }, - "animations" : { - "type" : "array", - "items" : { "$ref": "#/definitions/spine_animation" } } }, "definitions" : { - "spine_animation" : { - "type" : "object", - "required" : [ "track", "name" ], - "additionalProperties" : false, - "properties" : { - "track" : { "type" : "integer", "minimum" : 0, "maximum": 8 }, - "name" : { "$ref": "#/common_definitions/name" }, - "loop" : { "type" : "boolean" }, - "delay" : { "type" : "number" } - } - }, "spine_attachment" : { "type" : "object", "required" : [ "slot", "name" ], @@ -241,49 +163,6 @@ namespace e2d } } - if ( ctx.root.HasMember("animations") ) { - const auto& animations_json = ctx.root["animations"]; - E2D_ASSERT(animations_json.IsArray()); - - for ( rapidjson::SizeType i = 0; i < animations_json.Size(); ++i ) { - E2D_ASSERT(animations_json[i].IsObject()); - const auto& item_json = animations_json[i]; - - E2D_ASSERT(item_json.HasMember("track") && item_json.HasMember("name")); - u32 track = 0; - secf delay; - bool loop = false; - const char* name = ""; - - if ( item_json.HasMember("name") ) { - name = item_json["name"].GetString(); - } - - if ( item_json.HasMember("track") ) { - if ( !json_utils::try_parse_value(item_json["track"], track) ) { - the().error("SPINE_PLAYER: Incorrect formatting of 'animations.track' property"); - return false; - } - } - - if ( item_json.HasMember("delay") ) { - if ( !json_utils::try_parse_value(item_json["delay"], delay.value) ) { - the().error("SPINE_PLAYER: Incorrect formatting of 'animations.delay' property"); - return false; - } - } - - if ( item_json.HasMember("loop") ) { - if ( !json_utils::try_parse_value(item_json["loop"], loop) ) { - the().error("SPINE_PLAYER: Incorrect formatting of 'animations.loop' property"); - return false; - } - } - - component.add_animation(track, name, loop, delay); - } - } - return true; } diff --git a/sources/enduro2d/high/starter.cpp b/sources/enduro2d/high/starter.cpp index c589944e..60d67ba5 100644 --- a/sources/enduro2d/high/starter.cpp +++ b/sources/enduro2d/high/starter.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -142,6 +143,7 @@ namespace e2d .register_component("renderer") .register_component("scene") .register_component("spine_player") + .register_component("spine_animation") .register_component("sprite_renderer"); safe_module_initialize(params.library_root(), the()); safe_module_initialize(); diff --git a/sources/enduro2d/high/systems/render_system_impl/render_system_drawer.cpp b/sources/enduro2d/high/systems/render_system_impl/render_system_drawer.cpp index 331b1ee5..f5a7983d 100644 --- a/sources/enduro2d/high/systems/render_system_impl/render_system_drawer.cpp +++ b/sources/enduro2d/high/systems/render_system_impl/render_system_drawer.cpp @@ -264,9 +264,8 @@ namespace e2d::render_system_impl return; } - spSkeleton* skeleton = spine_r.skeleton().operator->(); - spSkeletonClipping* clipper = spine_r.clipper().operator->(); - spVertexEffect* effect = spine_r.effect().operator->(); + spSkeleton* skeleton = spine_r.skeleton().get(); + spSkeletonClipping* clipper = spine_r.clipper().get(); const material_asset::ptr& src_mat = node_r.materials().front(); const bool use_premultiplied_alpha = spine_r.model()->content().premultiplied_alpha(); @@ -279,10 +278,6 @@ namespace e2d::render_system_impl if ( skeleton->color.a == 0 ) { return; } - - if ( effect ) { - effect->begin(effect, skeleton); - } const m4f& sm = node->world_matrix(); @@ -431,10 +426,6 @@ namespace e2d::render_system_impl } spSkeletonClipping_clipEnd2(clipper); - - if ( effect ) { - effect->end(effect); - } property_cache_.clear(); } diff --git a/sources/enduro2d/high/systems/spine_system.cpp b/sources/enduro2d/high/systems/spine_system.cpp index 605695ca..56884c62 100644 --- a/sources/enduro2d/high/systems/spine_system.cpp +++ b/sources/enduro2d/high/systems/spine_system.cpp @@ -7,26 +7,170 @@ #include #include +#include -#include -#include +#include + +namespace +{ + // spine will allocate storage for each track, so keep number of track as small as possible + constexpr e2d::u32 max_track_count = 8; +} namespace e2d { - spine_system::spine_system() {} + // + // internal_state + // - spine_system::~spine_system() noexcept { - spAnimationState_disposeStatics(); + class spine_system::internal_state final { + public: + void clear_events(ecs::registry& owner) const; + void process_new_animations(ecs::registry& owner); + void update_animations(ecs::registry& owner); + + void add_event(spTrackEntry* entry, const str& ev_name); + private: + struct animation_visitor_; + static void event_listener_(spAnimationState* state, spEventType type, spTrackEntry* entry, spEvent* event); + private: + flat_map anim_events_; + ecs::registry* owner_ = nullptr; + }; + + // + // animation_visitor_ + // + + struct spine_system::internal_state::animation_visitor_ { + animation_visitor_( + spSkeletonData* skeleton, + spAnimationState* anim_state, + internal_state& state) + : skeleton_(skeleton) + , anim_state_(anim_state) + , state_(state) {} + + void operator()(const spine_animation_event::clear_track& cmd) const noexcept { + E2D_ASSERT(cmd.track < max_track_count); + spAnimationState_clearTrack(anim_state_, cmd.track); + } + + void operator()(const spine_animation_event::set_anim& cmd) const noexcept { + E2D_ASSERT(cmd.track < max_track_count); + + spAnimation* anim = spSkeletonData_findAnimation(skeleton_, cmd.name.c_str()); + if ( !anim ) { + the().error("SPINE_PLAYER: animation '%0' is not found", cmd.name); + return; + } + spTrackEntry* entry = spAnimationState_setAnimation(anim_state_, cmd.track, anim, cmd.loop); + if ( entry && !cmd.on_complete.empty() ) { + state_.add_event(entry, cmd.on_complete); + } + } + + void operator()(const spine_animation_event::add_anim& cmd) const noexcept { + E2D_ASSERT(cmd.track < max_track_count); + + spAnimation* anim = spSkeletonData_findAnimation(skeleton_, cmd.name.c_str()); + if ( !anim ) { + the().error("SPINE_PLAYER: animation '%0' is not found", cmd.name); + return; + } + spTrackEntry* entry = spAnimationState_addAnimation(anim_state_, cmd.track, anim, cmd.loop, cmd.delay.value); + if ( entry && !cmd.on_complete.empty() ) { + state_.add_event(entry, cmd.on_complete); + } + } + + void operator()(const spine_animation_event::empty_anim& cmd) const noexcept { + E2D_ASSERT(cmd.track < max_track_count); + spTrackEntry* entry = spAnimationState_addEmptyAnimation(anim_state_, cmd.track, cmd.duration.value, cmd.delay.value); + if ( entry && !cmd.on_complete.empty() ) { + state_.add_event(entry, cmd.on_complete); + } + } + private: + spSkeletonData* skeleton_; + spAnimationState* anim_state_; + internal_state& state_; + }; + + void spine_system::internal_state::add_event(spTrackEntry* entry, const str& ev_name) { + anim_events_.insert_or_assign(entry, ev_name); + entry->listener = &event_listener_; + entry->userData = this; + } + + void spine_system::internal_state::event_listener_(spAnimationState* state, spEventType type, spTrackEntry* entry, spEvent* event) { + if ( !entry || !entry->userData || type == SP_ANIMATION_DISPOSE ) { + return; + } + + internal_state* self = reinterpret_cast(entry->userData); + auto entry_to_event = self->anim_events_.find(entry); + if ( entry_to_event == self->anim_events_.end() ) { + return; + } + + ecs::entity_id id(size_t(state->userData)); + ecs::entity ent(*self->owner_, id); + + switch ( type ) { + case SP_ANIMATION_COMPLETE: { + auto* comp = ent.find_component(); + if ( !comp ) { + comp = &ent.assign_component(); + } + comp->completed().push_back(std::move(entry_to_event->second)); + + self->anim_events_.erase(entry_to_event); + break; + } + } + } + + void spine_system::internal_state::clear_events(ecs::registry& owner) const { + owner.remove_all_components(); } - void spine_system::process(ecs::registry& owner) { - float dt = the().delta_time(); - owner.for_each_component([dt]( - const ecs::const_entity&, + void spine_system::internal_state::process_new_animations(ecs::registry& owner) { + owner_ = &owner; + owner.for_joined_components( + [this, &owner]( + ecs::entity_id id, + const spine_animation_event& events, spine_player& player) { - spSkeleton* skeleton = player.skeleton().operator->(); - spAnimationState* anim_state = player.animation().operator->(); + spSkeleton* skeleton = player.skeleton().get(); + spAnimationState* anim_state = player.animation().get(); + + if ( !skeleton || !anim_state ) { + return; + } + + anim_state->userData = reinterpret_cast(size_t(id)); + + animation_visitor_ vis(skeleton->data, anim_state, *this); + for ( auto& ev : events.commands() ) { + stdex::visit(vis, ev); + } + }); + owner.remove_all_components(); + owner_ = nullptr; + } + + void spine_system::internal_state::update_animations(ecs::registry& owner) { + const float dt = the().delta_time(); + owner_ = &owner; + owner.for_each_component( + [dt]( + ecs::entity_id id, + spine_player& player) + { + spSkeleton* skeleton = player.skeleton().get(); + spAnimationState* anim_state = player.animation().get(); if ( !skeleton || !anim_state ) { return; @@ -37,5 +181,19 @@ namespace e2d spAnimationState_apply(anim_state, skeleton); spSkeleton_updateWorldTransform(skeleton); }); + owner_ = nullptr; + } + + spine_system::spine_system() + : state_(new internal_state()) {} + + spine_system::~spine_system() noexcept { + spAnimationState_disposeStatics(); + } + + void spine_system::process(ecs::registry& owner) { + state_->clear_events(owner); + state_->process_new_animations(owner); + state_->update_animations(owner); } }