#pragma once #include "script.hpp" #include "compat/assert.hpp" #include #include #include // ReSharper disable CppDFAUnreachableCode namespace floormat::detail_Script { template concept BaseScript = requires (S& script, const std::shared_ptr& obj, size_t& i, const Ns& ns) { requires std::is_base_of_v; script.on_init(obj); script.on_update(obj, i, ns); script.on_destroy(obj, script_destroy_reason::COUNT); script.delete_self(); script.~S(); }; template requires requires () { requires BaseScript; requires std::is_base_of_v; } CORRADE_ALWAYS_INLINE void concept_check() {} } // namespace floormat::detail_Script namespace floormat { #define FM_ASSERT_SCRIPT_STATE(new_state) (base_script::_assert_state(_state, (new_state), __FILE__, __LINE__)) template Script::~Script() noexcept { fm_assert(_state < script_lifecycle::COUNT); switch (_state) { case script_lifecycle::no_init: case script_lifecycle::torn_down: _state = (script_lifecycle)-1; break; case script_lifecycle::COUNT: std::unreachable(); case script_lifecycle::created: case script_lifecycle::destroying: case script_lifecycle::initializing: fm_abort("invalid state '%s' in script destructor", base_script::state_name(_state).data()); } } template Script::Script(): ptr{nullptr}, _state{script_lifecycle::no_init} { detail_Script::concept_check(); } template script_lifecycle Script::state() const { return _state; } template Script::operator bool() const { return ptr; } template S* Script::operator->() { FM_ASSERT_SCRIPT_STATE(script_lifecycle::created); fm_debug_assert(ptr); return ptr; } template void Script::do_create(S* p) { if (!p) p = make_empty(); FM_ASSERT_SCRIPT_STATE(script_lifecycle::no_init); _state = script_lifecycle::initializing; ptr = p; } template void Script::do_create(Pointer p) { do_create(p.release()); } template void Script::do_initialize(const std::shared_ptr& obj) { switch (_state) { default: FM_ASSERT_SCRIPT_STATE(script_lifecycle::initializing); std::unreachable(); case script_lifecycle::no_init: ptr = make_empty(); [[fallthrough]]; case script_lifecycle::initializing: _state = script_lifecycle::created; ptr->on_init(obj); return; } std::unreachable(); } template void Script::do_reassign(S* p, const std::shared_ptr& obj) { if (!p) [[unlikely]] return do_clear(obj); FM_ASSERT_SCRIPT_STATE(script_lifecycle::created); fm_debug_assert(ptr); ptr->on_destroy(obj, script_destroy_reason::unassign); ptr->delete_self(); ptr = p; p->on_init(obj); } template void Script::do_reassign(Pointer p, const std::shared_ptr& obj) { return do_reassign(p.release(), obj); } template void Script::do_clear(const std::shared_ptr& obj) { FM_ASSERT_SCRIPT_STATE(script_lifecycle::created); fm_debug_assert(ptr); ptr->on_destroy(obj, script_destroy_reason::unassign); ptr->delete_self(); ptr = make_empty(); } template void Script::do_destroy_pre(const std::shared_ptr& obj, script_destroy_reason r) { FM_ASSERT_SCRIPT_STATE(script_lifecycle::created); _state = script_lifecycle::destroying; ptr->on_destroy(obj, r); } template void Script::do_finish_destroy() { FM_ASSERT_SCRIPT_STATE(script_lifecycle::destroying); _state = script_lifecycle::torn_down; ptr->delete_self(); ptr = nullptr; } template void Script::do_ensure_torn_down() { FM_ASSERT_SCRIPT_STATE(script_lifecycle::torn_down); } template void Script::do_error_unwind() { fm_assert(_state < script_lifecycle::COUNT); switch (_state) { using enum script_lifecycle; case COUNT: std::unreachable(); case created: case initializing: case destroying: ptr->delete_self(); ptr = nullptr; break; case no_init: case torn_down: break; } } #undef FM_ASSERT_SCRIPT_STATE } // namespace floormat