summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorStanislaw Halik <sthalik@misaki.pl>2024-04-08 11:16:28 +0200
committerStanislaw Halik <sthalik@misaki.pl>2024-04-08 19:40:24 +0200
commit457cdf24489c20f0f9b9b9877bf9e875aa643f91 (patch)
tree5c01a4cbe3935784e30b3a2a2912e62aa00ef29a
parentb25edf465d08750fb5f93068cd8b8d5cd9d0dcec (diff)
wip
-rw-r--r--editor/imgui.cpp5
-rw-r--r--editor/scenery-editor.cpp3
-rw-r--r--editor/vobj-editor.cpp3
-rw-r--r--src/chunk.cpp6
-rw-r--r--src/chunk.hpp1
-rw-r--r--src/critter-script-base.cpp4
-rw-r--r--src/critter-script.cpp41
-rw-r--r--src/critter-script.hpp38
-rw-r--r--src/critter-script.inl16
-rw-r--r--src/critter.cpp7
-rw-r--r--src/critter.hpp8
-rw-r--r--src/object.hpp1
-rw-r--r--src/script.cpp26
-rw-r--r--src/script.hpp67
-rw-r--r--src/script.inl172
-rw-r--r--src/world.cpp12
-rw-r--r--test/app.hpp1
-rw-r--r--test/script.cpp20
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