diff options
author | Stanislaw Halik <sthalik@misaki.pl> | 2024-04-08 11:16:28 +0200 |
---|---|---|
committer | Stanislaw Halik <sthalik@misaki.pl> | 2024-04-08 19:40:24 +0200 |
commit | 457cdf24489c20f0f9b9b9877bf9e875aa643f91 (patch) | |
tree | 5c01a4cbe3935784e30b3a2a2912e62aa00ef29a | |
parent | b25edf465d08750fb5f93068cd8b8d5cd9d0dcec (diff) |
wip
-rw-r--r-- | editor/imgui.cpp | 5 | ||||
-rw-r--r-- | editor/scenery-editor.cpp | 3 | ||||
-rw-r--r-- | editor/vobj-editor.cpp | 3 | ||||
-rw-r--r-- | src/chunk.cpp | 6 | ||||
-rw-r--r-- | src/chunk.hpp | 1 | ||||
-rw-r--r-- | src/critter-script-base.cpp | 4 | ||||
-rw-r--r-- | src/critter-script.cpp | 41 | ||||
-rw-r--r-- | src/critter-script.hpp | 38 | ||||
-rw-r--r-- | src/critter-script.inl | 16 | ||||
-rw-r--r-- | src/critter.cpp | 7 | ||||
-rw-r--r-- | src/critter.hpp | 8 | ||||
-rw-r--r-- | src/object.hpp | 1 | ||||
-rw-r--r-- | src/script.cpp | 26 | ||||
-rw-r--r-- | src/script.hpp | 67 | ||||
-rw-r--r-- | src/script.inl | 172 | ||||
-rw-r--r-- | src/world.cpp | 12 | ||||
-rw-r--r-- | test/app.hpp | 1 | ||||
-rw-r--r-- | test/script.cpp | 20 |
18 files changed, 376 insertions, 55 deletions
diff --git a/editor/imgui.cpp b/editor/imgui.cpp index 3867d227..9f43eda1 100644 --- a/editor/imgui.cpp +++ b/editor/imgui.cpp @@ -338,7 +338,7 @@ void app::do_popup_menu() { const auto [id, target] = _popup_target; auto& w = M->world(); - auto eʹ = w.find_object(id); + const auto eʹ = w.find_object(id); if (target == popup_target_type::none || !eʹ) { @@ -388,7 +388,10 @@ void app::do_popup_menu() ImGui::MenuItem("Rotate", nullptr, false, next_rot != e.r && e.can_rotate(next_rot))) e.rotate(i, next_rot); if (ImGui::MenuItem("Delete", nullptr, false)) + { + //e.on_destroy(eʹ, false); e.chunk().remove_object(e.index()); + } } else _popup_target = {}; diff --git a/editor/scenery-editor.cpp b/editor/scenery-editor.cpp index 07cdbd7c..7a99bbb0 100644 --- a/editor/scenery-editor.cpp +++ b/editor/scenery-editor.cpp @@ -92,8 +92,9 @@ start: while (auto id = a.get_object_colliding_with_cursor()) { for (auto i = 0uz; i < sz; i++) - if (es[i]->id == id) + if (const auto eʹ = es[i]; eʹ->id == id) { + //eʹ->on_destroy(eʹ, false); c.remove_object(i); goto start; } diff --git a/editor/vobj-editor.cpp b/editor/vobj-editor.cpp index f644ca1c..52365fd5 100644 --- a/editor/vobj-editor.cpp +++ b/editor/vobj-editor.cpp @@ -53,8 +53,9 @@ start: while (auto id = a.get_object_colliding_with_cursor()) { for (auto i = (int)(es.size()-1); i >= 0; i--) { - if (es[i]->id == id) + if (const auto eʹ = es[i]; eʹ->id == id) { + //eʹ->on_destroy(eʹ); c.remove_object((unsigned)i); goto start; } diff --git a/src/chunk.cpp b/src/chunk.cpp index 26b8779f..e2b26e72 100644 --- a/src/chunk.cpp +++ b/src/chunk.cpp @@ -170,6 +170,12 @@ void chunk::add_object(const std::shared_ptr<object>& e) arrayInsert(es, (size_t)std::distance(es.cbegin(), it), e); } +void chunk::on_teardown() +{ + fm_assert(!_teardown); // too late, some chunks were already erased + //for (const auto& eʹ : _objects) eʹ->on_destroy(eʹ, true); // todo! +} + void chunk::remove_object(size_t i) { fm_assert(_objects_sorted); diff --git a/src/chunk.hpp b/src/chunk.hpp index d5bde803..dea7e00b 100644 --- a/src/chunk.hpp +++ b/src/chunk.hpp @@ -111,6 +111,7 @@ public: void add_object_unsorted(const std::shared_ptr<object>& e); void sort_objects(); void remove_object(size_t i); + void on_teardown(); ArrayView<const std::shared_ptr<object>> objects() const; // for drawing only diff --git a/src/critter-script-base.cpp b/src/critter-script-base.cpp index 4808e7b5..a321d2cd 100644 --- a/src/critter-script-base.cpp +++ b/src/critter-script-base.cpp @@ -2,9 +2,7 @@ namespace floormat { -template class script_wrapper<critter_script>; -base_script::~base_script() noexcept = default; -base_script::base_script() noexcept = default; + } // namespace floormat diff --git a/src/critter-script.cpp b/src/critter-script.cpp index e2355a64..ede5c5d7 100644 --- a/src/critter-script.cpp +++ b/src/critter-script.cpp @@ -1,7 +1,46 @@ -#include "critter-script.hpp" +#include "critter-script.inl" +#include "compat/assert.hpp" namespace floormat { +namespace { +CORRADE_ALWAYS_INLINE +void touch_ptr(const std::shared_ptr<critter>& p) +{ + (void)p; +#if fm_ASAN + volatile char foo = *reinterpret_cast<volatile const char*>(&*p); + (void)foo; +//#else +// fm_debug_assert(p); +#endif +} + +struct empty_critter_script final : critter_script +{ + empty_critter_script(); + void on_init(const std::shared_ptr<critter>& c) override; + void on_update(const std::shared_ptr<critter>& c, size_t& i, const Ns& dt) override; + void on_destroy(const std::shared_ptr<critter>& c, script_destroy_reason reason) override; + void delete_self() noexcept override; +}; + +empty_critter_script::empty_critter_script() : critter_script{nullptr} {} +void empty_critter_script::on_init(const std::shared_ptr<critter>& p) { touch_ptr(p); } +void empty_critter_script::on_update(const std::shared_ptr<critter>& p, size_t&, const Ns&) { touch_ptr(p); } +void empty_critter_script::on_destroy(const std::shared_ptr<critter>& p, script_destroy_reason) { touch_ptr(p); } +void empty_critter_script::delete_self() noexcept {} + +empty_critter_script empty_script_ = {}; + +} // namespace + +critter_script* const critter_script::empty_script = &empty_script_; + +critter_script::critter_script(const std::shared_ptr<critter>&) {} +critter_script::~critter_script() noexcept {} + +template class Script<critter_script, critter>; } // namespace floormat diff --git a/src/critter-script.hpp b/src/critter-script.hpp index 676a8a73..75f8156d 100644 --- a/src/critter-script.hpp +++ b/src/critter-script.hpp @@ -1,40 +1,24 @@ #pragma once +#include "script.hpp" +#include <memory> namespace floormat { struct critter; struct Ns; -struct base_script -{ - virtual ~base_script() noexcept; - virtual void delete_self() = 0; - base_script() noexcept; -}; - -template<typename T> -class script_wrapper final -{ - static_assert(std::is_base_of_v<base_script, T>); - T* ptr; - -public: - explicit script_wrapper(T* ptr); - ~script_wrapper() noexcept; - - const T& operator*() const noexcept; - T& operator*() noexcept; - const T* operator->() const noexcept; - T* operator->() noexcept; -}; - struct critter_script : base_script { - critter_script(critter& c); - virtual void update(critter& c, size_t& i, const Ns& dt) = 0; + critter_script(const std::shared_ptr<critter>& c); + ~critter_script() noexcept override; + + virtual void on_init(const std::shared_ptr<critter>& c) = 0; + virtual void on_update(const std::shared_ptr<critter>& c, size_t& i, const Ns& dt) = 0; + virtual void on_destroy(const std::shared_ptr<critter>& c, script_destroy_reason reason) = 0; + virtual void delete_self() = 0; // todo can_activate, activate -}; -extern template class script_wrapper<critter_script>; + static critter_script* const empty_script; +}; } // namespace floormat diff --git a/src/critter-script.inl b/src/critter-script.inl index db6b7000..01ce1e46 100644 --- a/src/critter-script.inl +++ b/src/critter-script.inl @@ -1,23 +1,11 @@ #pragma once #include "critter-script.hpp" +#include "script.inl" +#include "critter.hpp" #include "compat/assert.hpp" namespace floormat { -template <typename T> script_wrapper<T>::script_wrapper(T* ptr): ptr{ptr} { fm_assert(ptr); } -template <typename T> -script_wrapper<T>::~script_wrapper() noexcept -{ - ptr->delete_self(); -#ifndef FM_NO_DEBUG - ptr = nullptr; -#endif -} - -template <typename T> const T& script_wrapper<T>::operator*() const noexcept { return *ptr; } -template <typename T> T& script_wrapper<T>::operator*() noexcept { return *ptr; } -template <typename T> const T* script_wrapper<T>::operator->() const noexcept { return ptr; } -template <typename T> T* script_wrapper<T>::operator->() noexcept { return ptr; } } // namespace floormat diff --git a/src/critter.cpp b/src/critter.cpp index 0fb3e406..ece278b2 100644 --- a/src/critter.cpp +++ b/src/critter.cpp @@ -316,6 +316,8 @@ constexpr float step_magnitude(Vector2b vec) } // namespace +extern template class Script<critter, critter_script>; + critter_proto::critter_proto(const critter_proto&) = default; critter_proto::~critter_proto() noexcept = default; critter_proto& critter_proto::operator=(const critter_proto&) = default; @@ -542,4 +544,9 @@ critter::critter(object_id id, class chunk& c, critter_proto proto) : object::set_bbox_(offset, bbox_offset, Vector2ub(iTILE_SIZE2/2), pass); } +critter::~critter() noexcept +{ + //fm_assert(!script); +} + } // namespace floormat diff --git a/src/critter.hpp b/src/critter.hpp index 9bf4529f..2026806c 100644 --- a/src/critter.hpp +++ b/src/critter.hpp @@ -1,12 +1,14 @@ #pragma once -#include "src/global-coords.hpp" -#include "src/object.hpp" +#include "global-coords.hpp" +#include "object.hpp" +#include "script.hpp" #include <Corrade/Containers/String.h> namespace floormat { class anim_atlas; class world; +struct critter_script; struct critter_proto : object_proto { @@ -26,6 +28,7 @@ struct critter final : object static constexpr double framerate = 60, move_speed = 60; static constexpr double frame_time = 1/framerate; + ~critter() noexcept override; object_type type() const noexcept override; explicit operator critter_proto() const; @@ -41,6 +44,7 @@ struct critter final : object Vector2 ordinal_offset(Vector2b offset) const override; float depth_offset() const override; + Script<critter_script, critter> script; String name; float speed = 1; uint16_t offset_frac_ = 0; diff --git a/src/object.hpp b/src/object.hpp index 4c535340..ce793fe8 100644 --- a/src/object.hpp +++ b/src/object.hpp @@ -6,6 +6,7 @@ #include "src/object-type.hpp" #include "src/object-id.hpp" #include "src/point.hpp" +#include "src/script.hpp" #include <memory> namespace floormat { diff --git a/src/script.cpp b/src/script.cpp new file mode 100644 index 00000000..57df6e58 --- /dev/null +++ b/src/script.cpp @@ -0,0 +1,26 @@ +#include "script.inl" +#include <cr/StringView.h> + +namespace floormat { + +namespace { + +constexpr StringView names[(size_t)script_lifecycle::COUNT] = +{ + "no-init"_s, "initializing"_s, "created"_s, "destroying"_s, "torn_down"_s, +}; + +} // namespace + +StringView base_script::state_name(script_lifecycle x) +{ + if (x >= script_lifecycle::COUNT) + fm_abort("invalid script_lifecycle value '%d'", (int)x); + else + return names[(uint32_t)x]; +} + +base_script::~base_script() noexcept = default; +base_script::base_script() noexcept = default; + +} // namespace floormat diff --git a/src/script.hpp b/src/script.hpp new file mode 100644 index 00000000..bb403eda --- /dev/null +++ b/src/script.hpp @@ -0,0 +1,67 @@ +#pragma once +#include "compat/defs.hpp" +#include <memory> + +namespace floormat +{ +struct object; +struct Ns; + +enum class script_lifecycle : uint8_t +{ + no_init, initializing, created, destroying, torn_down, COUNT, +}; + +struct base_script +{ + fm_DECLARE_DELETED_COPY_ASSIGNMENT(base_script); + fm_DECLARE_DELETED_MOVE_ASSIGNMENT(base_script); + + base_script() noexcept; + virtual ~base_script() noexcept; + + static StringView state_name(script_lifecycle x); +}; + +enum class script_destroy_reason : uint8_t +{ + quit, // game is being shut down + kill, // object is being deleted from the gameworld + unassign, // script is unassigned from object + COUNT, +}; + +template<typename S, typename Obj> +class Script final +{ + S* ptr; + script_lifecycle state; + void _assert_state(script_lifecycle s, const char* file, int line); + +public: + Script(); + ~Script() noexcept; + + // [no-init] -> do_create() -> [initializing] + // [initializing] -> do_initialize() -> [created] + // [created] -> call() -> [created] + // [created] -> do_reassign() -> [no-init] + // [created] -> do_destroy_pre() -> [destroying] + // [destroying] -> do_finish_destroy() -> [torn-down] + // [torn-down] -> do_ensure_torn_down() -> [torn-down] + // * -> do_error_unwind() -> [torn-down] + // [torn-down] -> ~script() + + S* operator->(); + + void do_create(S* ptr); + void do_initialize(const std::shared_ptr<Obj>& obj); + void do_reassign(S* ptr, const std::shared_ptr<Obj>& obj); + void do_destroy_1(const std::shared_ptr<Obj>& obj); + void do_destroy_pre(const std::shared_ptr<Obj>& obj); + void do_finish_destroy(); + void do_ensure_torn_down(); + void do_error_unwind(); +}; + +} // namespace floormat diff --git a/src/script.inl b/src/script.inl new file mode 100644 index 00000000..9e8cc40b --- /dev/null +++ b/src/script.inl @@ -0,0 +1,172 @@ +#pragma once +#include "script.hpp" +#include "compat/assert.hpp" + +#include <Corrade/Containers/StringView.h> + +// ReSharper disable CppDFAUnreachableCode + +namespace floormat::detail_Script { + +template<typename S, typename Obj> +concept BaseScript = +requires (S& script, const std::shared_ptr<Obj>& obj, size_t& i, const Ns& ns) +{ + requires std::is_base_of_v<base_script, S>; + script.on_init(obj); + script.on_update(obj, i, ns); + script.on_destroy(obj, script_destroy_reason::COUNT); + script.delete_self(); + script.~S(); +}; + +template<typename S, typename Obj> +requires requires () +{ + requires BaseScript<S, Obj>; + requires std::is_base_of_v<object, Obj>; +} +CORRADE_ALWAYS_INLINE +void concept_check() {} + +} // namespace floormat::detail_Script + +namespace floormat { + +#define FM_ASSERT_SCRIPT_STATE(new_state) (Script<S, Obj>::_assert_state((new_state), __FILE__, __LINE__)) + +template <typename S, typename Obj> +Script<S, Obj>::~Script() noexcept +{ + fm_assert(state < script_lifecycle::COUNT); + switch (state) + { + case script_lifecycle::no_init: + case script_lifecycle::torn_down: + state = (script_lifecycle)(unsigned)-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 <typename S, typename Obj> + +Script<S, Obj>::Script(): ptr{nullptr}, state{script_lifecycle::no_init} +{ + detail_Script::concept_check<S, Obj>(); +} + +template <typename S, typename Obj> +void Script<S, Obj>::_assert_state(script_lifecycle s, const char* file, int line) +{ + if (state != s) + { + fm_EMIT_DEBUG2("fatal: ", + "invalid state transition from '%s' to '%s'", + base_script::state_name(state).data(), + base_script::state_name(s).data()); + fm_EMIT_DEBUG("", " in %s:%d", file, line); + fm_EMIT_ABORT(); + } +} + +template <typename S, typename Obj> +S* Script<S, Obj>::operator->() +{ + FM_ASSERT_SCRIPT_STATE(script_lifecycle::created); + fm_debug_assert(ptr); + return ptr; +} + +template<typename S, typename Obj> +void Script<S, Obj>::do_create(S* p) +{ + fm_assert(p); + FM_ASSERT_SCRIPT_STATE(script_lifecycle::no_init); + state = script_lifecycle::initializing; + ptr = p; +} + +template <typename S, typename Obj> +void Script<S, Obj>::do_initialize(const std::shared_ptr<Obj>& obj) +{ + FM_ASSERT_SCRIPT_STATE(script_lifecycle::initializing); + state = script_lifecycle::created; + ptr->on_init(obj); +} + +template <typename S, typename Obj> +void Script<S, Obj>::do_reassign(S* p, const std::shared_ptr<Obj>& obj) +{ + fm_assert(p); + 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 <typename S, typename Obj> +void Script<S, Obj>::do_destroy_1(const std::shared_ptr<Obj>& obj) +{ + FM_ASSERT_SCRIPT_STATE(script_lifecycle::created); + state = script_lifecycle::torn_down; + ptr->on_destroy(obj, script_destroy_reason::kill); + ptr->delete_self(); + ptr = nullptr; +} + +template <typename S, typename Obj> +void Script<S, Obj>::do_destroy_pre(const std::shared_ptr<Obj>& obj) +{ + FM_ASSERT_SCRIPT_STATE(script_lifecycle::created); + state = script_lifecycle::destroying; + ptr->on_destroy(obj, script_destroy_reason::quit); +} + +template <typename S, typename Obj> +void Script<S, Obj>::do_finish_destroy() +{ + FM_ASSERT_SCRIPT_STATE(script_lifecycle::destroying); + state = script_lifecycle::torn_down; + ptr->delete_self(); + ptr = nullptr; +} + +template <typename S, typename Obj> +void Script<S, Obj>::do_ensure_torn_down() +{ + FM_ASSERT_SCRIPT_STATE(script_lifecycle::torn_down); +} + +template <typename S, typename Obj> +void Script<S, Obj>::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; + } + fm_assert(false); +} + +#undef FM_ASSERT_SCRIPT_STATE + +} // namespace floormat diff --git a/src/world.cpp b/src/world.cpp index 6fdb38ff..2b6407ae 100644 --- a/src/world.cpp +++ b/src/world.cpp @@ -65,14 +65,16 @@ world::world() : world{initial_capacity} world::~world() noexcept { + for (auto& [k, c] : _chunks) + c.on_teardown(); _teardown = true; - for (auto& [k, v] : _chunks) + for (auto& [k, c] : _chunks) { - v._teardown = true; - v.mark_scenery_modified(); - v.mark_passability_modified(); + c._teardown = true; + c.mark_scenery_modified(); + c.mark_passability_modified(); _last_chunk = {}; - arrayResize(v._objects, 0); + arrayResize(c._objects, 0); } _last_chunk = {}; _chunks.clear(); diff --git a/test/app.hpp b/test/app.hpp index 4f6597d9..6ec88371 100644 --- a/test/app.hpp +++ b/test/app.hpp @@ -41,6 +41,7 @@ struct test_app final : private FM_APPLICATION static void test_raycast(); static void test_region(); static void test_saves(); + static void test_script(); static void test_serializer1(); static void test_tile_iter(); static void test_wall_atlas(); diff --git a/test/script.cpp b/test/script.cpp new file mode 100644 index 00000000..45ce9b1d --- /dev/null +++ b/test/script.cpp @@ -0,0 +1,20 @@ +#include "app.hpp" + +namespace floormat { + +namespace { + +void test_script1() +{ + +} + +} // namespace + +void test_app::test_script() +{ + test_script1(); +} + + +} // namespace floormat |