mirror of
https://github.com/zhenyan121/Cubed.git
synced 2026-08-08 17:57:02 +08:00
feature: creature (#38)
* build(deps): add assimp library as dependency * feat(render): add model loading and rendering pipeline * refactor(render): rename depth player shaders to depth model and clean up * build: add EnTT library * feat(entity): add ECS-based entity rendering * feat(render): add shadow pass for entity models * refactor(render): unify model and player rendering with single shader pipeline * refactor(collision): convert AABB to center-half representation and migrate player data to ECS * refactor(gameplay): add base classes Chunk and World for shared logic * feat(gameplay): implement entity movement system with collision detection - Add Velocity and HitBoxes components to entity - Introduce HitboxManager for loading per-entity collision AABBs from JSON - Create MoveSystem with per-axis collision handling - Move get_block_aabb to base World class and add virtual get_per_tick_time - Remove static get_block_aabb from ClientWorld; use member m_per_tick_time for tick duration * fix(gameplay): correct hitbox insertion in HitboxManager::load * build: remove entt library * refactor(gameplay): replace entt components with SparseVector for player data Refactor client world to use a custom SparseVector for player data instead of entt registry. Consolidate Transform, ViewAngles, and related structs into Position, Orientation, WalkPose. Introduce PlayerData and PlayerRenderData. Remove the unused move_system.cpp. Unify player render and shadow render into a single function. * feat(render): add model ID system with concurrent lookup and namespace-based loading - Extract `ModelID` and `Model` struct to new `model.hpp` - Replace `std::unordered_map` with `tbb::concurrent_hash_map` for thread safety - Add `get_model(ModelID)`, `get_model_id`, `get_model_name` methods - Parse model names in `namespace:name` format to construct asset paths - Update `load_model` to accept `string_view` and use ID-based management * refactor(gameplay): extract movement, gravity, orientation, and walk pose into structs * refactor(gameplay): extract Entity base class from ClientPlayer Move position, walk pose, velocity, orientation, movement, and gravity fields and their accessors into a new Entity base class. ClientPlayer now inherits from Entity, removing duplicated members. Also update velocity handling to use 3D vector per axis and adjust related logic. * feat(player): split max speed into horizontal/vertical, set spawn pos * refactor: move movement logic into SpeedSystem and Entity components * refactor(gameplay): extract physics and collision from ClientPlayer into PhysicalSystem Move per-axis collision detection and move distance calculation to new PhysicalSystem. Move SpeedSystem implementation from inline header to separate .cpp file. Add const accessors to Entity. Replace inline AABB helper with HitboxManager registration of player hitbox using new PLAYER_SIZE constant. Remove obsolete members and functions from ClientPlayer. * refactor(gameplay): integrate model and hitbox ID system into Entity - Introduce HitboxID, EntityID, and ModelID types for safer ID-based lookups - Replace AABB struct with Hitbox (includes HitboxID) - Convert ModelManager and HitboxManager to singletons with Handle structs - Update Entity to store IDs for model and hitbox, removing direct references - Reorganize creature model assets into subdirectories per entity type - Add player model (player.glb) and collision data for pig - Remove ModelManager dependency from App and Renderer - Add namespace parsing utility for asset paths - Mark sparse_vector::insert() with [[nodiscard]] * build: add entt library * refactor(gameplay): rename ClientPlayer to LocalPlayer * refactor(gameplay): separate player logic into manager and ECS components * feat(gameplay): add entity managers and refactor to ECS components * refactor(gameplay): replace client thread with timer-based system and add entity manager * feat(gameplay): implement entity system with concurrent task handling and fix model loading * feat(gameplay): add entity destruction support * feat(entity): add client-to-server entity creation and destruction requests Extend the network protocol with `C2SEntityCreateRequest` and `C2SEntityDestoryRequest` packets. Refactor entity managers to split public client-facing create/destroy methods that send requests over the network, from internal handlers that process received packets. Add utility functions for converting protobuf Vec3 to glm::vec3. * feat(gameplay): implement entity update packets Add S2CEntityUpdate to sync entity positions from server to client, including update handling in the client entity manager and server-side AI updates. * feat(ai): add wander AI system with move boost Add AIBase and WanderAITag components, a WanderAISystem, and a MoveBoost component to control wandering behavior. Replace the old MoveState with MoveBoost and include a horizontal random direction helper. * refactor(ecs): move entity physics to tick-based systems Update PhysicalSystem and SpeedSystem to operate on entt::registry instead of individual components. Add TickVelocity for server creatures so movement is calculated per tick without frame delta time. LocalPlayer now implements its own client-side physics with collision detection. * feat(gameplay): add server-side entity movement and AI Refactor LocalPlayer::update_physical to operate on a passed position, and run speed, physical, and wander AI systems in ServerEntityManager. * feat(gameplay): add pig wander AI and refine speed physics * feat(server): send entity position updates to all sessions * fix: correct velocity clamping and hitbox loading Preserve sign when clamping velocity; negative velocities now clamp to zero instead of flipping direction. Support loading hitbox definitions from JSON arrays and objects. Tune pig entity movement, spawn height, and wander probabilities. * feat(creatures): refine pig movement physics Add movement constants for pigs and switch deceleration to per-axis friction so creatures stop naturally. Extend wander boost duration for smoother behavior. * feat(physics): implement step-up for horizontal collisions * feat: sync entity direction and rotate models accordingly Move direction into the transform component and include it in server-to-client entity updates. Client now sets transform direction from the network message and uses it to compute yaw for model rotation, so entities visually face their movement direction. Refactor net_utils to support arbitrary Vec3 fields. * feat(render): interpolate entity transforms for rendering Add RenderTransform component to smooth position/direction updates and use it in entity and shadow passes. * fix(server_world): send time before entity updates * build: replace nlohmann json with rapidjson * refactor(json): migrate from nlohmann to rapidjson Replace nlohmann::json with rapidjson across localization, hitbox manager, server world, and sensitive filter. Add json_utils helper for converting rapidjson documents to maps, and improve parse error handling. * feat(item): add item manager with JSON asset loading Implement ItemManager to load and query item definitions from assets/item JSON files. Add ItemData struct, item asset metadata, CMake source registration, and initialize the manager during app startup. Also add AGENTS.md repository guidelines. * refactor(item): decouple items from block types The item system now supports an ItemKind and property, allowing item JSON to declare a type instead of assuming every item is a block. BlockManager was moved to its own header, redesigned around concurrent hash maps, and exposes id_from_name(). Texture and UI code now key items by ItemID, while block placement resolves the block type from the item registry. * fix(gameplay): use inline const for static EMPTY members Use inline const for static EMPTY members to avoid ODR and linker issues. Add missing block_manager.hpp includes in gameplay sources. * fix: stabilize block item registration and display Store item names as owned strings in ItemManager, set block type property for block items, validate item kind before placement, and handle items without textures gracefully in inventory UI. * feat: add pig spawn egg item Add pig spawn egg asset and texture, parse spawn egg item kind and creature property, and spawn the configured entity when used on an empty block. Also fix item texture loading to use actual image dimensions and update selected item UI sizing. * refactor(gameplay): update systems per entity with chunk check Refactor server entity update flow to process entities individually, skipping those whose chunk is not loaded. System update methods now accept a single entity instead of iterating the full registry view, and `ServerWorld::get_chunk_ref_count` is added to determine if an entity's chunk is active. * perf(render): skip entities outside loaded chunks * feat: add --direct-enter option to skip to world scene Support a new CLI flag that launches the app directly into the world scene. If no IP is provided, it starts a local server on the specified port and connects to 127.0.0.1; otherwise it connects to the given IP. With this flag enabled, a port must be specified via -p. Also expose SceneManager::push as public so scenes can be pushed immediately when bypassing the normal menu flow. * refactor(localization): switch block translations to item naming Remove the `name_key` field from block definitions and use item-based localization keys (`item.*.name`) for inventory display. Item data now stores the localized name at load time. * feat(gameplay): spawn creatures during chunk generation Add creature spawning to chunk generation with a configurable SpawnConfig, including a default pig spawn. Spawning occurs in the final generation phase and registers entities through the server world's entity manager. Also destroy entities when their chunk is unloaded to prevent orphaned entities and expose the entity manager from ServerWorld. * feat(gameplay): add run mode based thread pool config Introduce RunMode enum and thread pool sizing helpers for client, server, and hybrid modes. Add compute pool to server world and pass mode through server/client initialization. * perf(server): parallelize entity update loop Use parallel_do to process entities concurrently via the compute pool. Switch get_all_session to a thread-safe tbb::concurrent_vector and make ChunkEntity's ref_count atomic to avoid data races. Also remove the debug pig spawn from world init. * feat(creatures): animate creature walk cycles Add a Gait component synchronized over the network and procedural animation for model nodes. Load animation parameters from assets/model/creature/pig/animation.json and apply leg swing, body bob, and head motion based on gait. Refactor Gait into its own header for reuse. * fix(gameplay): make entity interpolation frame-rate independent * perf(render): batch model rendering with instancing Replaces per-entity draw calls with instanced rendering for both main and shadow passes. Each model's node hierarchy is flattened once and instance matrices are updated per frame, reducing draw calls and CPU overhead. * refactor(render): build entity instance buffers once per frame Move instance matrix upload into build_vertices and use a precomputed instance data map for both shadow and color passes, avoiding duplicate GPU buffer updates. * perf(gameplay): batch entity updates into single packet Aggregate per-entity update messages into S2CEntityUpdateBatch and broadcast once per tick. Reuse serialized packets in several broadcast loops to avoid repeated make_packet calls. * fix: correct thread count and shadow projection uniform - Add reserved client thread count to client threads in server generation calculation. - Set projection matrix uniform only for non-shadow rendering. * feat(ecs): add entity type separation and creature limits Replace generic entity creation with typed creatures and items. Rename `add_entity` to `add_creature`, enforce per-player creature cap, and track entity/creature totals for metrics. Expose run mode to dev panel and show new counts. * perf(render): frustum cull entities before instance building Add frustum culling to entity instance data collection using AABB vs camera frustum planes. Pass renderer into get_instances_data_map to compute MVP and extract planes. Also add "Rendered Entities" debug counter for visibility. * feat(audio): add ambient pig sounds Add pig call audio with randomized timing, triggered by proximity to the player as 3D positional sound. * feat(client-entity): use snapshot history for entity interpolation Replace exponential smoothing with a buffered snapshot system. Each entity keeps a deque of position/direction snapshots with timestamps; the render transform is interpolated at a fixed 100 ms render delay to hide network jitter. Snapshots are capped to 16 entries. * refactor(hitbox): move player hitbox definition to JSON asset * feat(client_player): use snapshot interpolation for remote players Replace exponential position smoothing with snapshot interpolation. Remote player transforms are rendered from a delayed snapshot history, using linear interpolation for position and shortest-path interpolation for yaw/pitch. A 100 ms render delay compensates for network tick rate.
This commit is contained in:
314
include/entt/signal/delegate.hpp
Normal file
314
include/entt/signal/delegate.hpp
Normal file
@@ -0,0 +1,314 @@
|
||||
#ifndef ENTT_SIGNAL_DELEGATE_HPP
|
||||
#define ENTT_SIGNAL_DELEGATE_HPP
|
||||
|
||||
#include "../config/config.h"
|
||||
#include "../core/type_traits.hpp"
|
||||
#include "../stl/cstddef.hpp"
|
||||
#include "../stl/functional.hpp"
|
||||
#include "../stl/tuple.hpp"
|
||||
#include "../stl/type_traits.hpp"
|
||||
#include "../stl/utility.hpp"
|
||||
#include "fwd.hpp"
|
||||
|
||||
namespace entt {
|
||||
|
||||
/*! @cond ENTT_INTERNAL */
|
||||
namespace internal {
|
||||
|
||||
template<typename Ret, typename... Args>
|
||||
auto function_pointer(Ret (*)(Args...)) -> Ret (*)(Args...);
|
||||
|
||||
template<typename Ret, typename Type, typename... Args, typename Other>
|
||||
auto function_pointer(Ret (*)(Type, Args...), Other &&) -> Ret (*)(Args...);
|
||||
|
||||
template<typename Class, typename Ret, typename... Args, typename... Other>
|
||||
auto function_pointer(Ret (Class::*)(Args...), Other &&...) -> Ret (*)(Args...);
|
||||
|
||||
template<typename Class, typename Ret, typename... Args, typename... Other>
|
||||
auto function_pointer(Ret (Class::*)(Args...) const, Other &&...) -> Ret (*)(Args...);
|
||||
|
||||
template<typename Class, typename Type, typename... Other>
|
||||
requires stl::is_member_object_pointer_v<Type Class::*>
|
||||
auto function_pointer(Type Class::*, Other &&...) -> Type (*)();
|
||||
|
||||
template<typename... Type>
|
||||
using function_pointer_t = decltype(function_pointer(stl::declval<Type>()...));
|
||||
|
||||
template<typename... Class, typename Ret, typename... Args>
|
||||
[[nodiscard]] ENTT_CONSTEVAL auto index_sequence_for(Ret (*)(Args...)) {
|
||||
return stl::index_sequence_for<Class..., Args...>{};
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
/*! @endcond */
|
||||
|
||||
/**
|
||||
* @brief Basic delegate implementation.
|
||||
*
|
||||
* Primary template isn't defined on purpose. All the specializations give a
|
||||
* compile-time error unless the template parameter is a function type.
|
||||
*/
|
||||
template<typename>
|
||||
class delegate;
|
||||
|
||||
/**
|
||||
* @brief Utility class to use to send around functions and members.
|
||||
*
|
||||
* Unmanaged delegate for function pointers and members. Users of this class are
|
||||
* in charge of disconnecting instances before deleting them.
|
||||
*
|
||||
* A delegate can be used as a general purpose invoker without memory overhead
|
||||
* for free functions possibly with payloads and bound or unbound members.
|
||||
*
|
||||
* @tparam Ret Return type of a function type.
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
*/
|
||||
template<typename Ret, typename... Args>
|
||||
class delegate<Ret(Args...)> {
|
||||
using return_type = stl::remove_const_t<Ret>;
|
||||
using delegate_type = return_type(const void *, Args...);
|
||||
|
||||
template<auto Candidate, stl::size_t... Index>
|
||||
[[nodiscard]] auto wrap(stl::index_sequence<Index...>) noexcept {
|
||||
return [](const void *, Args... args) -> return_type {
|
||||
[[maybe_unused]] const auto arguments = stl::forward_as_tuple(stl::forward<Args>(args)...);
|
||||
[[maybe_unused]] constexpr auto offset = !stl::is_invocable_r_v<Ret, decltype(Candidate), type_list_element_t<Index, type_list<Args...>>...> * (sizeof...(Args) - sizeof...(Index));
|
||||
return static_cast<Ret>(stl::invoke(Candidate, stl::forward<type_list_element_t<Index + offset, type_list<Args...>>>(stl::get<Index + offset>(arguments))...));
|
||||
};
|
||||
}
|
||||
|
||||
template<auto Candidate, typename Type, stl::size_t... Index>
|
||||
[[nodiscard]] auto wrap(Type &, stl::index_sequence<Index...>) noexcept {
|
||||
return [](const void *payload, Args... args) -> return_type {
|
||||
Type *curr = static_cast<Type *>(const_cast<constness_as_t<void, Type> *>(payload));
|
||||
[[maybe_unused]] const auto arguments = stl::forward_as_tuple(stl::forward<Args>(args)...);
|
||||
[[maybe_unused]] constexpr auto offset = !stl::is_invocable_r_v<Ret, decltype(Candidate), Type &, type_list_element_t<Index, type_list<Args...>>...> * (sizeof...(Args) - sizeof...(Index));
|
||||
return static_cast<Ret>(stl::invoke(Candidate, *curr, stl::forward<type_list_element_t<Index + offset, type_list<Args...>>>(stl::get<Index + offset>(arguments))...));
|
||||
};
|
||||
}
|
||||
|
||||
template<auto Candidate, typename Type, stl::size_t... Index>
|
||||
[[nodiscard]] auto wrap(Type *, stl::index_sequence<Index...>) noexcept {
|
||||
return [](const void *payload, Args... args) -> return_type {
|
||||
Type *curr = static_cast<Type *>(const_cast<constness_as_t<void, Type> *>(payload));
|
||||
[[maybe_unused]] const auto arguments = stl::forward_as_tuple(stl::forward<Args>(args)...);
|
||||
[[maybe_unused]] constexpr auto offset = !stl::is_invocable_r_v<Ret, decltype(Candidate), Type *, type_list_element_t<Index, type_list<Args...>>...> * (sizeof...(Args) - sizeof...(Index));
|
||||
return static_cast<Ret>(stl::invoke(Candidate, curr, stl::forward<type_list_element_t<Index + offset, type_list<Args...>>>(stl::get<Index + offset>(arguments))...));
|
||||
};
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Function type of the contained target. */
|
||||
using function_type = Ret(const void *, Args...);
|
||||
/*! @brief Function type of the delegate. */
|
||||
using type = Ret(Args...);
|
||||
/*! @brief Return type of the delegate. */
|
||||
using result_type = Ret;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
delegate() noexcept = default;
|
||||
|
||||
/**
|
||||
* @brief Constructs a delegate with a given object or payload, if any.
|
||||
* @tparam Candidate Function or member to connect to the delegate.
|
||||
* @tparam Type Type of class or type of payload, if any.
|
||||
* @param value_or_instance Optional valid object that fits the purpose.
|
||||
*/
|
||||
template<auto Candidate, typename... Type>
|
||||
delegate(connect_arg_t<Candidate>, Type &&...value_or_instance) noexcept {
|
||||
connect<Candidate>(stl::forward<Type>(value_or_instance)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Constructs a delegate and connects an user defined function with
|
||||
* optional payload.
|
||||
* @param function Function to connect to the delegate.
|
||||
* @param payload User defined arbitrary data.
|
||||
*/
|
||||
delegate(function_type *function, const void *payload = nullptr) noexcept {
|
||||
connect(function, payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a free function or an unbound member to a delegate.
|
||||
* @tparam Candidate Function or member to connect to the delegate.
|
||||
*/
|
||||
template<auto Candidate>
|
||||
void connect() noexcept {
|
||||
instance = nullptr;
|
||||
|
||||
if constexpr(stl::is_invocable_r_v<Ret, decltype(Candidate), Args...>) {
|
||||
fn = [](const void *, Args... args) -> return_type {
|
||||
return Ret(stl::invoke(Candidate, stl::forward<Args>(args)...));
|
||||
};
|
||||
} else if constexpr(stl::is_member_pointer_v<decltype(Candidate)>) {
|
||||
fn = wrap<Candidate>(internal::index_sequence_for<type_list_element_t<0, type_list<Args...>>>(internal::function_pointer_t<decltype(Candidate)>{}));
|
||||
} else {
|
||||
fn = wrap<Candidate>(internal::index_sequence_for(internal::function_pointer_t<decltype(Candidate)>{}));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a free function with payload or a bound member to a
|
||||
* delegate.
|
||||
*
|
||||
* The delegate isn't responsible for the connected object or the payload.
|
||||
* Users must always guarantee that the lifetime of the instance overcomes
|
||||
* the one of the delegate.<br/>
|
||||
* When used to connect a free function with payload, its signature must be
|
||||
* such that the instance is the first argument before the ones used to
|
||||
* define the delegate itself.
|
||||
*
|
||||
* @tparam Candidate Function or member to connect to the delegate.
|
||||
* @tparam Type Type of class or type of payload.
|
||||
* @param value_or_instance A valid reference that fits the purpose.
|
||||
*/
|
||||
template<auto Candidate, typename Type>
|
||||
void connect(Type &value_or_instance) noexcept {
|
||||
instance = &value_or_instance;
|
||||
|
||||
if constexpr(stl::is_invocable_r_v<Ret, decltype(Candidate), Type &, Args...>) {
|
||||
fn = [](const void *payload, Args... args) -> return_type {
|
||||
Type *curr = static_cast<Type *>(const_cast<constness_as_t<void, Type> *>(payload));
|
||||
return Ret(stl::invoke(Candidate, *curr, stl::forward<Args>(args)...));
|
||||
};
|
||||
} else {
|
||||
fn = wrap<Candidate>(value_or_instance, internal::index_sequence_for(internal::function_pointer_t<decltype(Candidate), Type>{}));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a free function with payload or a bound member to a
|
||||
* delegate.
|
||||
*
|
||||
* @sa connect(Type &)
|
||||
*
|
||||
* @tparam Candidate Function or member to connect to the delegate.
|
||||
* @tparam Type Type of class or type of payload.
|
||||
* @param value_or_instance A valid pointer that fits the purpose.
|
||||
*/
|
||||
template<auto Candidate, typename Type>
|
||||
void connect(Type *value_or_instance) noexcept {
|
||||
instance = value_or_instance;
|
||||
|
||||
if constexpr(stl::is_invocable_r_v<Ret, decltype(Candidate), Type *, Args...>) {
|
||||
fn = [](const void *payload, Args... args) -> return_type {
|
||||
Type *curr = static_cast<Type *>(const_cast<constness_as_t<void, Type> *>(payload));
|
||||
return Ret(stl::invoke(Candidate, curr, stl::forward<Args>(args)...));
|
||||
};
|
||||
} else {
|
||||
fn = wrap<Candidate>(value_or_instance, internal::index_sequence_for(internal::function_pointer_t<decltype(Candidate), Type>{}));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects an user defined function with optional payload to a
|
||||
* delegate.
|
||||
*
|
||||
* The delegate isn't responsible for the connected object or the payload.
|
||||
* Users must always guarantee that the lifetime of an instance overcomes
|
||||
* the one of the delegate.<br/>
|
||||
* The payload is returned as the first argument to the target function in
|
||||
* all cases.
|
||||
*
|
||||
* @param function Function to connect to the delegate.
|
||||
* @param payload User defined arbitrary data.
|
||||
*/
|
||||
void connect(function_type *function, const void *payload = nullptr) noexcept {
|
||||
ENTT_ASSERT(function != nullptr, "Uninitialized function pointer");
|
||||
instance = payload;
|
||||
fn = function;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Resets a delegate.
|
||||
*
|
||||
* After a reset, a delegate cannot be invoked anymore.
|
||||
*/
|
||||
void reset() noexcept {
|
||||
instance = nullptr;
|
||||
fn = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a pointer to the stored callable function target, if any.
|
||||
* @return An opaque pointer to the stored callable function target.
|
||||
*/
|
||||
[[nodiscard]] function_type *target() const noexcept {
|
||||
return fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the instance or the payload linked to a delegate, if any.
|
||||
* @return An opaque pointer to the underlying data.
|
||||
*/
|
||||
[[nodiscard]] const void *data() const noexcept {
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Triggers a delegate.
|
||||
*
|
||||
* The delegate invokes the underlying function and returns the result.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to trigger an invalid delegate results in undefined
|
||||
* behavior.
|
||||
*
|
||||
* @param args Arguments to use to invoke the underlying function.
|
||||
* @return The value returned by the underlying function.
|
||||
*/
|
||||
Ret operator()(Args... args) const {
|
||||
ENTT_ASSERT(static_cast<bool>(*this), "Uninitialized delegate");
|
||||
return fn(instance, stl::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks whether a delegate actually stores a listener.
|
||||
* @return False if the delegate is empty, true otherwise.
|
||||
*/
|
||||
[[nodiscard]] explicit operator bool() const noexcept {
|
||||
// no need to also test instance
|
||||
return !(fn == nullptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compares the contents of two delegates.
|
||||
* @param other Delegate with which to compare.
|
||||
* @return False if the two contents differ, true otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool operator==(const delegate<Ret(Args...)> &other) const noexcept {
|
||||
return fn == other.fn && instance == other.instance;
|
||||
}
|
||||
|
||||
private:
|
||||
const void *instance{};
|
||||
delegate_type *fn{};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Deduction guide.
|
||||
* @tparam Candidate Function or member to connect to the delegate.
|
||||
*/
|
||||
template<auto Candidate>
|
||||
delegate(connect_arg_t<Candidate>) -> delegate<stl::remove_pointer_t<internal::function_pointer_t<decltype(Candidate)>>>;
|
||||
|
||||
/**
|
||||
* @brief Deduction guide.
|
||||
* @tparam Candidate Function or member to connect to the delegate.
|
||||
* @tparam Type Type of class or type of payload.
|
||||
*/
|
||||
template<auto Candidate, typename Type>
|
||||
delegate(connect_arg_t<Candidate>, Type &&) -> delegate<stl::remove_pointer_t<internal::function_pointer_t<decltype(Candidate), Type>>>;
|
||||
|
||||
/**
|
||||
* @brief Deduction guide.
|
||||
* @tparam Ret Return type of a function type.
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
*/
|
||||
template<typename Ret, typename... Args>
|
||||
delegate(Ret (*)(const void *, Args...), const void * = nullptr) -> delegate<Ret(Args...)>;
|
||||
|
||||
} // namespace entt
|
||||
|
||||
#endif
|
||||
391
include/entt/signal/dispatcher.hpp
Normal file
391
include/entt/signal/dispatcher.hpp
Normal file
@@ -0,0 +1,391 @@
|
||||
#ifndef ENTT_SIGNAL_DISPATCHER_HPP
|
||||
#define ENTT_SIGNAL_DISPATCHER_HPP
|
||||
|
||||
#include "../container/dense_map.hpp"
|
||||
#include "../core/compressed_pair.hpp"
|
||||
#include "../core/concepts.hpp"
|
||||
#include "../core/fwd.hpp"
|
||||
#include "../core/type_info.hpp"
|
||||
#include "../stl/cstddef.hpp"
|
||||
#include "../stl/functional.hpp"
|
||||
#include "../stl/memory.hpp"
|
||||
#include "../stl/type_traits.hpp"
|
||||
#include "../stl/utility.hpp"
|
||||
#include "../stl/vector.hpp"
|
||||
#include "fwd.hpp"
|
||||
#include "sigh.hpp"
|
||||
|
||||
namespace entt {
|
||||
|
||||
/*! @cond ENTT_INTERNAL */
|
||||
namespace internal {
|
||||
|
||||
struct basic_dispatcher_handler {
|
||||
virtual ~basic_dispatcher_handler() = default;
|
||||
virtual void publish() = 0;
|
||||
virtual void disconnect(void *) = 0;
|
||||
virtual void clear() noexcept = 0;
|
||||
[[nodiscard]] virtual stl::size_t size() const noexcept = 0;
|
||||
};
|
||||
|
||||
template<cvref_unqualified Type, typename Allocator>
|
||||
class dispatcher_handler final: public basic_dispatcher_handler {
|
||||
using alloc_traits = stl::allocator_traits<Allocator>;
|
||||
using signal_type = sigh<void(Type &), Allocator>;
|
||||
using container_type = stl::vector<Type, typename alloc_traits::template rebind_alloc<Type>>;
|
||||
|
||||
public:
|
||||
using allocator_type = Allocator;
|
||||
|
||||
dispatcher_handler(const allocator_type &allocator)
|
||||
: signal{allocator},
|
||||
events{allocator} {}
|
||||
|
||||
void publish() override {
|
||||
container_type other{};
|
||||
other.swap(events);
|
||||
|
||||
for(auto &&elem: other) {
|
||||
signal.publish(elem);
|
||||
}
|
||||
}
|
||||
|
||||
void disconnect(void *instance) override {
|
||||
bucket().disconnect(instance);
|
||||
}
|
||||
|
||||
void clear() noexcept override {
|
||||
events.clear();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto bucket() noexcept {
|
||||
return typename signal_type::sink_type{signal};
|
||||
}
|
||||
|
||||
void trigger(Type &event) {
|
||||
signal.publish(event);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
void enqueue(Args &&...args) {
|
||||
if constexpr(stl::is_aggregate_v<Type> && (sizeof...(Args) != 0u || !stl::is_default_constructible_v<Type>)) {
|
||||
events.push_back(Type{stl::forward<Args>(args)...});
|
||||
} else {
|
||||
events.emplace_back(stl::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] stl::size_t size() const noexcept override {
|
||||
return events.size();
|
||||
}
|
||||
|
||||
private:
|
||||
signal_type signal;
|
||||
container_type events;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
/*! @endcond */
|
||||
|
||||
/**
|
||||
* @brief Basic dispatcher implementation.
|
||||
*
|
||||
* A dispatcher can be used either to trigger an immediate event or to enqueue
|
||||
* events to be published all together once per tick.<br/>
|
||||
* Listeners are provided in the form of member functions. For each event of
|
||||
* type `Type`, listeners are such that they can be invoked with an argument of
|
||||
* type `Type &`, no matter what the return type is.
|
||||
*
|
||||
* The dispatcher creates instances of the `sigh` class internally. Refer to the
|
||||
* documentation of the latter for more details.
|
||||
*
|
||||
* @tparam Allocator Type of allocator used to manage memory and elements.
|
||||
*/
|
||||
template<typename Allocator>
|
||||
class basic_dispatcher {
|
||||
template<typename Type>
|
||||
using handler_type = internal::dispatcher_handler<Type, Allocator>;
|
||||
|
||||
using key_type = id_type;
|
||||
// stl::shared_ptr because of its type erased allocator which is useful here
|
||||
using mapped_type = stl::shared_ptr<internal::basic_dispatcher_handler>;
|
||||
|
||||
using alloc_traits = stl::allocator_traits<Allocator>;
|
||||
using container_allocator = alloc_traits::template rebind_alloc<stl::pair<const key_type, mapped_type>>;
|
||||
using container_type = dense_map<key_type, mapped_type, stl::identity, stl::equal_to<>, container_allocator>;
|
||||
|
||||
template<cvref_unqualified Type>
|
||||
[[nodiscard]] handler_type<Type> &assure(const id_type id) {
|
||||
auto &&ptr = pools.first()[id];
|
||||
|
||||
if(!ptr) {
|
||||
const auto &allocator = get_allocator();
|
||||
ptr = stl::allocate_shared<handler_type<Type>>(allocator, allocator);
|
||||
}
|
||||
|
||||
return static_cast<handler_type<Type> &>(*ptr);
|
||||
}
|
||||
|
||||
template<cvref_unqualified Type>
|
||||
[[nodiscard]] const handler_type<Type> *assure(const id_type id) const {
|
||||
if(auto it = pools.first().find(id); it != pools.first().cend()) {
|
||||
return static_cast<const handler_type<Type> *>(it->second.get());
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Allocator type. */
|
||||
using allocator_type = Allocator;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = stl::size_t;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
basic_dispatcher()
|
||||
: basic_dispatcher{allocator_type{}} {}
|
||||
|
||||
/**
|
||||
* @brief Constructs a dispatcher with a given allocator.
|
||||
* @param allocator The allocator to use.
|
||||
*/
|
||||
explicit basic_dispatcher(const allocator_type &allocator)
|
||||
: pools{allocator, allocator} {}
|
||||
|
||||
/*! @brief Default copy constructor, deleted on purpose. */
|
||||
basic_dispatcher(const basic_dispatcher &) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
* @param other The instance to move from.
|
||||
*/
|
||||
basic_dispatcher(basic_dispatcher &&other) noexcept
|
||||
: pools{stl::move(other.pools)} {}
|
||||
|
||||
/**
|
||||
* @brief Allocator-extended move constructor.
|
||||
* @param other The instance to move from.
|
||||
* @param allocator The allocator to use.
|
||||
*/
|
||||
basic_dispatcher(basic_dispatcher &&other, const allocator_type &allocator)
|
||||
: pools{container_type{stl::move(other.pools.first()), allocator}, allocator} {
|
||||
ENTT_ASSERT(alloc_traits::is_always_equal::value || get_allocator() == other.get_allocator(), "Copying a dispatcher is not allowed");
|
||||
}
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
~basic_dispatcher() = default;
|
||||
|
||||
/**
|
||||
* @brief Default copy assignment operator, deleted on purpose.
|
||||
* @return This dispatcher.
|
||||
*/
|
||||
basic_dispatcher &operator=(const basic_dispatcher &) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move assignment operator.
|
||||
* @param other The instance to move from.
|
||||
* @return This dispatcher.
|
||||
*/
|
||||
basic_dispatcher &operator=(basic_dispatcher &&other) noexcept {
|
||||
ENTT_ASSERT(alloc_traits::is_always_equal::value || get_allocator() == other.get_allocator(), "Copying a dispatcher is not allowed");
|
||||
swap(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Exchanges the contents with those of a given dispatcher.
|
||||
* @param other Dispatcher to exchange the content with.
|
||||
*/
|
||||
void swap(basic_dispatcher &other) noexcept {
|
||||
using stl::swap;
|
||||
swap(pools, other.pools);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the associated allocator.
|
||||
* @return The associated allocator.
|
||||
*/
|
||||
[[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
|
||||
return pools.second();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the number of pending events for a given type.
|
||||
* @tparam Type Type of event for which to return the count.
|
||||
* @param id Name used to map the event queue within the dispatcher.
|
||||
* @return The number of pending events for the given type.
|
||||
*/
|
||||
template<typename Type>
|
||||
[[nodiscard]] size_type size(const id_type id = type_hash<Type>::value()) const noexcept {
|
||||
const auto *cpool = assure<stl::decay_t<Type>>(id);
|
||||
return cpool ? cpool->size() : 0u;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the total number of pending events.
|
||||
* @return The total number of pending events.
|
||||
*/
|
||||
[[nodiscard]] size_type size() const noexcept {
|
||||
size_type count{};
|
||||
|
||||
for(auto &&cpool: pools.first()) {
|
||||
count += cpool.second->size();
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a sink object for the given event and queue.
|
||||
*
|
||||
* A sink is an opaque object used to connect listeners to events.
|
||||
*
|
||||
* The function type for a listener is _compatible_ with:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void(Type &);
|
||||
* @endcode
|
||||
*
|
||||
* The order of invocation of the listeners isn't guaranteed.
|
||||
*
|
||||
* @sa sink
|
||||
*
|
||||
* @tparam Type Type of event of which to get the sink.
|
||||
* @param id Name used to map the event queue within the dispatcher.
|
||||
* @return A temporary sink object.
|
||||
*/
|
||||
template<typename Type>
|
||||
[[nodiscard]] auto sink(const id_type id = type_hash<Type>::value()) {
|
||||
return assure<Type>(id).bucket();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Triggers an immediate event of a given type.
|
||||
* @tparam Type Type of event to trigger.
|
||||
* @param value An instance of the given type of event.
|
||||
*/
|
||||
template<typename Type>
|
||||
void trigger(Type value) {
|
||||
trigger(type_hash<stl::decay_t<Type>>::value(), value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Triggers an immediate event on a queue of a given type.
|
||||
* @tparam Type Type of event to trigger.
|
||||
* @param value An instance of the given type of event.
|
||||
* @param id Name used to map the event queue within the dispatcher.
|
||||
*/
|
||||
template<typename Type>
|
||||
void trigger(const id_type id, Type value) {
|
||||
assure<stl::decay_t<Type>>(id).trigger(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enqueues an event of the given type.
|
||||
* @tparam Type Type of event to enqueue.
|
||||
* @tparam Args Types of arguments to use to construct the event.
|
||||
* @param args Arguments to use to construct the event.
|
||||
*/
|
||||
template<typename Type, typename... Args>
|
||||
void enqueue(Args &&...args) {
|
||||
enqueue_hint<Type>(type_hash<Type>::value(), stl::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enqueues an event of the given type.
|
||||
* @tparam Type Type of event to enqueue.
|
||||
* @param value An instance of the given type of event.
|
||||
*/
|
||||
template<typename Type>
|
||||
void enqueue(Type &&value) {
|
||||
enqueue_hint(type_hash<stl::decay_t<Type>>::value(), stl::forward<Type>(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enqueues an event of the given type.
|
||||
* @tparam Type Type of event to enqueue.
|
||||
* @tparam Args Types of arguments to use to construct the event.
|
||||
* @param id Name used to map the event queue within the dispatcher.
|
||||
* @param args Arguments to use to construct the event.
|
||||
*/
|
||||
template<typename Type, typename... Args>
|
||||
void enqueue_hint(const id_type id, Args &&...args) {
|
||||
assure<Type>(id).enqueue(stl::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enqueues an event of the given type.
|
||||
* @tparam Type Type of event to enqueue.
|
||||
* @param id Name used to map the event queue within the dispatcher.
|
||||
* @param value An instance of the given type of event.
|
||||
*/
|
||||
template<typename Type>
|
||||
void enqueue_hint(const id_type id, Type &&value) {
|
||||
assure<stl::decay_t<Type>>(id).enqueue(stl::forward<Type>(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Utility function to disconnect everything related to a given value
|
||||
* or instance from a dispatcher.
|
||||
* @tparam Type Type of class or type of payload.
|
||||
* @param value_or_instance A valid object that fits the purpose.
|
||||
*/
|
||||
template<typename Type>
|
||||
void disconnect(Type &value_or_instance) {
|
||||
disconnect(&value_or_instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Utility function to disconnect everything related to a given value
|
||||
* or instance from a dispatcher.
|
||||
* @tparam Type Type of class or type of payload.
|
||||
* @param value_or_instance A valid object that fits the purpose.
|
||||
*/
|
||||
template<typename Type>
|
||||
void disconnect(Type *value_or_instance) {
|
||||
for(auto &&cpool: pools.first()) {
|
||||
cpool.second->disconnect(value_or_instance);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Discards all the events stored so far in a given queue.
|
||||
* @tparam Type Type of event to discard.
|
||||
* @param id Name used to map the event queue within the dispatcher.
|
||||
*/
|
||||
template<typename Type>
|
||||
void clear(const id_type id = type_hash<Type>::value()) {
|
||||
assure<Type>(id).clear();
|
||||
}
|
||||
|
||||
/*! @brief Discards all the events queued so far. */
|
||||
void clear() noexcept {
|
||||
for(auto &&cpool: pools.first()) {
|
||||
cpool.second->clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Delivers all the pending events of a given queue.
|
||||
* @tparam Type Type of event to send.
|
||||
* @param id Name used to map the event queue within the dispatcher.
|
||||
*/
|
||||
template<typename Type>
|
||||
void update(const id_type id = type_hash<Type>::value()) {
|
||||
assure<Type>(id).publish();
|
||||
}
|
||||
|
||||
/*! @brief Delivers all the pending events. */
|
||||
void update() const {
|
||||
for(auto &&cpool: pools.first()) {
|
||||
cpool.second->publish();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
compressed_pair<container_type, allocator_type> pools;
|
||||
};
|
||||
|
||||
} // namespace entt
|
||||
|
||||
#endif
|
||||
181
include/entt/signal/emitter.hpp
Normal file
181
include/entt/signal/emitter.hpp
Normal file
@@ -0,0 +1,181 @@
|
||||
#ifndef ENTT_SIGNAL_EMITTER_HPP
|
||||
#define ENTT_SIGNAL_EMITTER_HPP
|
||||
|
||||
#include "../container/dense_map.hpp"
|
||||
#include "../core/compressed_pair.hpp"
|
||||
#include "../core/fwd.hpp"
|
||||
#include "../core/type_info.hpp"
|
||||
#include "../stl/functional.hpp"
|
||||
#include "../stl/type_traits.hpp"
|
||||
#include "../stl/utility.hpp"
|
||||
#include "fwd.hpp"
|
||||
|
||||
namespace entt {
|
||||
|
||||
/**
|
||||
* @brief General purpose event emitter.
|
||||
*
|
||||
* To create an emitter type, derived classes must inherit from the base as:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* struct my_emitter: emitter<my_emitter> {
|
||||
* // ...
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* Handlers for the different events are created internally on the fly. It's not
|
||||
* required to specify in advance the full list of accepted events.<br/>
|
||||
* Moreover, whenever an event is published, an emitter also passes a reference
|
||||
* to itself to its listeners.
|
||||
*
|
||||
* @tparam Derived Emitter type.
|
||||
* @tparam Allocator Type of allocator used to manage memory and elements.
|
||||
*/
|
||||
template<typename Derived, typename Allocator>
|
||||
class emitter {
|
||||
using key_type = id_type;
|
||||
using mapped_type = stl::function<void(void *)>;
|
||||
|
||||
using alloc_traits = stl::allocator_traits<Allocator>;
|
||||
using container_allocator = alloc_traits::template rebind_alloc<stl::pair<const key_type, mapped_type>>;
|
||||
using container_type = dense_map<key_type, mapped_type, stl::identity, stl::equal_to<>, container_allocator>;
|
||||
|
||||
public:
|
||||
/*! @brief Allocator type. */
|
||||
using allocator_type = Allocator;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = stl::size_t;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
emitter()
|
||||
: emitter{allocator_type{}} {}
|
||||
|
||||
/**
|
||||
* @brief Constructs an emitter with a given allocator.
|
||||
* @param allocator The allocator to use.
|
||||
*/
|
||||
explicit emitter(const allocator_type &allocator)
|
||||
: handlers{allocator, allocator} {}
|
||||
|
||||
/*! @brief Default copy constructor, deleted on purpose. */
|
||||
emitter(const emitter &) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
* @param other The instance to move from.
|
||||
*/
|
||||
emitter(emitter &&other) noexcept
|
||||
: handlers{stl::move(other.handlers)} {}
|
||||
|
||||
/**
|
||||
* @brief Allocator-extended move constructor.
|
||||
* @param other The instance to move from.
|
||||
* @param allocator The allocator to use.
|
||||
*/
|
||||
emitter(emitter &&other, const allocator_type &allocator)
|
||||
: handlers{container_type{stl::move(other.handlers.first()), allocator}, allocator} {
|
||||
ENTT_ASSERT(alloc_traits::is_always_equal::value || handlers.second() == other.handlers.second(), "Copying an emitter is not allowed");
|
||||
}
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
virtual ~emitter() {
|
||||
static_assert(stl::is_base_of_v<emitter<Derived, Allocator>, Derived>, "Invalid emitter type");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Default copy assignment operator, deleted on purpose.
|
||||
* @return This emitter.
|
||||
*/
|
||||
emitter &operator=(const emitter &) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move assignment operator.
|
||||
* @param other The instance to move from.
|
||||
* @return This emitter.
|
||||
*/
|
||||
emitter &operator=(emitter &&other) noexcept {
|
||||
ENTT_ASSERT(alloc_traits::is_always_equal::value || handlers.second() == other.handlers.second(), "Copying an emitter is not allowed");
|
||||
swap(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Exchanges the contents with those of a given emitter.
|
||||
* @param other Emitter to exchange the content with.
|
||||
*/
|
||||
void swap(emitter &other) noexcept {
|
||||
using stl::swap;
|
||||
swap(handlers, other.handlers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the associated allocator.
|
||||
* @return The associated allocator.
|
||||
*/
|
||||
[[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
|
||||
return handlers.second();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Publishes a given event.
|
||||
* @tparam Type Type of event to trigger.
|
||||
* @param value An instance of the given type of event.
|
||||
*/
|
||||
template<typename Type>
|
||||
void publish(Type value) {
|
||||
if(const auto id = type_id<Type>().hash(); handlers.first().contains(id)) {
|
||||
handlers.first()[id](&value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Registers a listener with the event emitter.
|
||||
* @tparam Type Type of event to which to connect the listener.
|
||||
* @param func The listener to register.
|
||||
*/
|
||||
template<typename Type>
|
||||
void on(stl::function<void(Type &, Derived &)> func) {
|
||||
handlers.first().insert_or_assign(type_id<Type>().hash(), [func = stl::move(func), this](void *value) {
|
||||
func(*static_cast<Type *>(value), static_cast<Derived &>(*this));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects a listener from the event emitter.
|
||||
* @tparam Type Type of event of the listener.
|
||||
*/
|
||||
template<typename Type>
|
||||
void erase() {
|
||||
handlers.first().erase(type_hash<stl::remove_cvref_t<Type>>::value());
|
||||
}
|
||||
|
||||
/*! @brief Disconnects all the listeners. */
|
||||
void clear() noexcept {
|
||||
handlers.first().clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if there are listeners registered for the specific event.
|
||||
* @tparam Type Type of event to test.
|
||||
* @return True if there are no listeners registered, false otherwise.
|
||||
*/
|
||||
template<typename Type>
|
||||
[[nodiscard]] bool contains() const {
|
||||
return handlers.first().contains(type_hash<stl::remove_cvref_t<Type>>::value());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if there are listeners registered with the event emitter.
|
||||
* @return True if there are no listeners registered, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool empty() const noexcept {
|
||||
return handlers.first().empty();
|
||||
}
|
||||
|
||||
private:
|
||||
compressed_pair<container_type, allocator_type> handlers;
|
||||
};
|
||||
|
||||
} // namespace entt
|
||||
|
||||
#endif
|
||||
46
include/entt/signal/fwd.hpp
Normal file
46
include/entt/signal/fwd.hpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#ifndef ENTT_SIGNAL_FWD_HPP
|
||||
#define ENTT_SIGNAL_FWD_HPP
|
||||
|
||||
#include "../stl/memory.hpp"
|
||||
|
||||
namespace entt {
|
||||
|
||||
template<typename>
|
||||
class delegate;
|
||||
|
||||
template<typename = stl::allocator<void>>
|
||||
class basic_dispatcher;
|
||||
|
||||
template<typename, typename = stl::allocator<void>>
|
||||
class emitter;
|
||||
|
||||
class connection;
|
||||
|
||||
struct scoped_connection;
|
||||
|
||||
template<typename>
|
||||
class sink;
|
||||
|
||||
template<typename Type, typename = stl::allocator<void>>
|
||||
class sigh;
|
||||
|
||||
/*! @brief Alias declaration for the most common use case. */
|
||||
using dispatcher = basic_dispatcher<>;
|
||||
|
||||
/*! @brief Disambiguation tag for constructors and the like. */
|
||||
template<auto>
|
||||
struct connect_arg_t {
|
||||
/*! @brief Default constructor. */
|
||||
explicit connect_arg_t() = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Constant of type connect_arg_t used to disambiguate calls.
|
||||
* @tparam Candidate Element to connect (likely a free or member function).
|
||||
*/
|
||||
template<auto Candidate>
|
||||
inline constexpr connect_arg_t<Candidate> connect_arg{};
|
||||
|
||||
} // namespace entt
|
||||
|
||||
#endif
|
||||
573
include/entt/signal/sigh.hpp
Normal file
573
include/entt/signal/sigh.hpp
Normal file
@@ -0,0 +1,573 @@
|
||||
#ifndef ENTT_SIGNAL_SIGH_HPP
|
||||
#define ENTT_SIGNAL_SIGH_HPP
|
||||
|
||||
#include "../stl/cstddef.hpp"
|
||||
#include "../stl/memory.hpp"
|
||||
#include "../stl/type_traits.hpp"
|
||||
#include "../stl/utility.hpp"
|
||||
#include "../stl/vector.hpp"
|
||||
#include "delegate.hpp"
|
||||
#include "fwd.hpp"
|
||||
|
||||
namespace entt {
|
||||
|
||||
/**
|
||||
* @brief Sink class.
|
||||
*
|
||||
* Primary template isn't defined on purpose. All the specializations give a
|
||||
* compile-time error unless the template parameter is a function type.
|
||||
*
|
||||
* @tparam Type A valid signal handler type.
|
||||
*/
|
||||
template<typename Type>
|
||||
class sink;
|
||||
|
||||
/**
|
||||
* @brief Unmanaged signal handler.
|
||||
*
|
||||
* Primary template isn't defined on purpose. All the specializations give a
|
||||
* compile-time error unless the template parameter is a function type.
|
||||
*
|
||||
* @tparam Type A valid function type.
|
||||
* @tparam Allocator Type of allocator used to manage memory and elements.
|
||||
*/
|
||||
template<typename Type, typename Allocator>
|
||||
class sigh;
|
||||
|
||||
/**
|
||||
* @brief Unmanaged signal handler.
|
||||
*
|
||||
* It works directly with references to classes and pointers to member functions
|
||||
* as well as pointers to free functions. Users of this class are in charge of
|
||||
* disconnecting instances before deleting them.
|
||||
*
|
||||
* This class serves mainly two purposes:
|
||||
*
|
||||
* * Creating signals to use later to notify a bunch of listeners.
|
||||
* * Collecting results from a set of functions like in a voting system.
|
||||
*
|
||||
* @tparam Ret Return type of a function type.
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
* @tparam Allocator Type of allocator used to manage memory and elements.
|
||||
*/
|
||||
template<typename Ret, typename... Args, typename Allocator>
|
||||
class sigh<Ret(Args...), Allocator> {
|
||||
friend class sink<sigh<Ret(Args...), Allocator>>;
|
||||
|
||||
using alloc_traits = stl::allocator_traits<Allocator>;
|
||||
using delegate_type = delegate<Ret(Args...)>;
|
||||
using container_type = stl::vector<delegate_type, typename alloc_traits::template rebind_alloc<delegate_type>>;
|
||||
|
||||
public:
|
||||
/*! @brief Allocator type. */
|
||||
using allocator_type = Allocator;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = stl::size_t;
|
||||
/*! @brief Sink type. */
|
||||
using sink_type = sink<sigh<Ret(Args...), Allocator>>;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
sigh() noexcept(noexcept(allocator_type{}))
|
||||
: sigh{allocator_type{}} {}
|
||||
|
||||
/**
|
||||
* @brief Constructs a signal handler with a given allocator.
|
||||
* @param allocator The allocator to use.
|
||||
*/
|
||||
explicit sigh(const allocator_type &allocator) noexcept
|
||||
: calls{allocator} {}
|
||||
|
||||
/**
|
||||
* @brief Copy constructor.
|
||||
* @param other The instance to copy from.
|
||||
*/
|
||||
sigh(const sigh &other)
|
||||
: calls{other.calls} {}
|
||||
|
||||
/**
|
||||
* @brief Allocator-extended copy constructor.
|
||||
* @param other The instance to copy from.
|
||||
* @param allocator The allocator to use.
|
||||
*/
|
||||
sigh(const sigh &other, const allocator_type &allocator)
|
||||
: calls{other.calls, allocator} {}
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
* @param other The instance to move from.
|
||||
*/
|
||||
sigh(sigh &&other) noexcept
|
||||
: calls{stl::move(other.calls)} {}
|
||||
|
||||
/**
|
||||
* @brief Allocator-extended move constructor.
|
||||
* @param other The instance to move from.
|
||||
* @param allocator The allocator to use.
|
||||
*/
|
||||
sigh(sigh &&other, const allocator_type &allocator)
|
||||
: calls{stl::move(other.calls), allocator} {}
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
~sigh() = default;
|
||||
|
||||
/**
|
||||
* @brief Copy assignment operator.
|
||||
* @param other The instance to copy from.
|
||||
* @return This signal handler.
|
||||
*/
|
||||
sigh &operator=(const sigh &other) {
|
||||
calls = other.calls;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Move assignment operator.
|
||||
* @param other The instance to move from.
|
||||
* @return This signal handler.
|
||||
*/
|
||||
sigh &operator=(sigh &&other) noexcept {
|
||||
swap(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Exchanges the contents with those of a given signal handler.
|
||||
* @param other Signal handler to exchange the content with.
|
||||
*/
|
||||
void swap(sigh &other) noexcept {
|
||||
using stl::swap;
|
||||
swap(calls, other.calls);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the associated allocator.
|
||||
* @return The associated allocator.
|
||||
*/
|
||||
[[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
|
||||
return calls.get_allocator();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Number of listeners connected to the signal.
|
||||
* @return Number of listeners currently connected.
|
||||
*/
|
||||
[[nodiscard]] size_type size() const noexcept {
|
||||
return calls.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns false if at least a listener is connected to the signal.
|
||||
* @return True if the signal has no listeners connected, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool empty() const noexcept {
|
||||
return calls.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Triggers a signal.
|
||||
*
|
||||
* All the listeners are notified. Order isn't guaranteed.
|
||||
*
|
||||
* @param args Arguments to use to invoke listeners.
|
||||
*/
|
||||
void publish(Args... args) const {
|
||||
for(auto pos = calls.size(); pos; --pos) {
|
||||
calls[pos - 1u](args...);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Collects return values from the listeners.
|
||||
*
|
||||
* The collector must expose a call operator with the following properties:
|
||||
*
|
||||
* * The return type is either `void` or such that it's convertible to
|
||||
* `bool`. In the second case, a true value will stop the iteration.
|
||||
* * The list of parameters is empty if `Ret` is `void`, otherwise it
|
||||
* contains a single element such that `Ret` is convertible to it.
|
||||
*
|
||||
* @tparam Func Type of collector to use, if any.
|
||||
* @param func A valid function object.
|
||||
* @param args Arguments to use to invoke listeners.
|
||||
*/
|
||||
template<typename Func>
|
||||
void collect(Func func, Args... args) const {
|
||||
for(auto pos = calls.size(); pos; --pos) {
|
||||
if constexpr(stl::is_void_v<Ret> || !stl::is_invocable_v<Func, Ret>) {
|
||||
calls[pos - 1u](args...);
|
||||
|
||||
if constexpr(stl::is_invocable_r_v<bool, Func>) {
|
||||
if(func()) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
func();
|
||||
}
|
||||
} else if constexpr(stl::is_invocable_r_v<bool, Func, Ret>) {
|
||||
if(func(calls[pos - 1u](args...))) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
func(calls[pos - 1u](args...));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
container_type calls;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Connection class.
|
||||
*
|
||||
* Opaque object the aim of which is to allow users to release an already
|
||||
* established connection without having to keep a reference to the signal or
|
||||
* the sink that generated it.
|
||||
*/
|
||||
class connection {
|
||||
template<typename>
|
||||
friend class sink;
|
||||
|
||||
connection(delegate<void(void *)> fn, void *ref)
|
||||
: disconnect{fn}, signal{ref} {}
|
||||
|
||||
public:
|
||||
/*! @brief Default constructor. */
|
||||
connection()
|
||||
: signal{} {}
|
||||
|
||||
/**
|
||||
* @brief Checks whether a connection is properly initialized.
|
||||
* @return True if the connection is properly initialized, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] explicit operator bool() const noexcept {
|
||||
return static_cast<bool>(disconnect);
|
||||
}
|
||||
|
||||
/*! @brief Breaks the connection. */
|
||||
void release() {
|
||||
if(disconnect) {
|
||||
disconnect(signal);
|
||||
disconnect.reset();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
delegate<void(void *)> disconnect;
|
||||
void *signal;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Scoped connection class.
|
||||
*
|
||||
* Opaque object the aim of which is to allow users to release an already
|
||||
* established connection without having to keep a reference to the signal or
|
||||
* the sink that generated it.<br/>
|
||||
* A scoped connection automatically breaks the link between the two objects
|
||||
* when it goes out of scope.
|
||||
*/
|
||||
struct scoped_connection {
|
||||
/*! @brief Default constructor. */
|
||||
scoped_connection() = default;
|
||||
|
||||
/**
|
||||
* @brief Constructs a scoped connection from a basic connection.
|
||||
* @param other A valid connection object.
|
||||
*/
|
||||
scoped_connection(const connection &other)
|
||||
: conn{other} {}
|
||||
|
||||
/*! @brief Default copy constructor, deleted on purpose. */
|
||||
scoped_connection(const scoped_connection &) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
* @param other The scoped connection to move from.
|
||||
*/
|
||||
scoped_connection(scoped_connection &&other) noexcept
|
||||
: conn{stl::exchange(other.conn, {})} {}
|
||||
|
||||
/*! @brief Automatically breaks the link on destruction. */
|
||||
~scoped_connection() {
|
||||
conn.release();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Default copy assignment operator, deleted on purpose.
|
||||
* @return This scoped connection.
|
||||
*/
|
||||
scoped_connection &operator=(const scoped_connection &) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move assignment operator.
|
||||
* @param other The scoped connection to move from.
|
||||
* @return This scoped connection.
|
||||
*/
|
||||
scoped_connection &operator=(scoped_connection &&other) noexcept {
|
||||
conn = stl::exchange(other.conn, {});
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Acquires a connection.
|
||||
* @param other The connection object to acquire.
|
||||
* @return This scoped connection.
|
||||
*/
|
||||
scoped_connection &operator=(connection other) {
|
||||
conn = other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks whether a scoped connection is properly initialized.
|
||||
* @return True if the connection is properly initialized, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] explicit operator bool() const noexcept {
|
||||
return static_cast<bool>(conn);
|
||||
}
|
||||
|
||||
/*! @brief Breaks the connection. */
|
||||
void release() {
|
||||
conn.release();
|
||||
}
|
||||
|
||||
private:
|
||||
connection conn;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Sink class.
|
||||
*
|
||||
* A sink is used to connect listeners to signals and to disconnect them.<br/>
|
||||
* The function type for a listener is the one of the signal to which it
|
||||
* belongs.
|
||||
*
|
||||
* The clear separation between a signal and a sink permits to store the former
|
||||
* as private data member without exposing the publish functionality to the
|
||||
* users of the class.
|
||||
*
|
||||
* @warning
|
||||
* Lifetime of a sink must not overcome that of the signal to which it refers.
|
||||
* In any other case, attempting to use a sink results in undefined behavior.
|
||||
*
|
||||
* @tparam Ret Return type of a function type.
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
* @tparam Allocator Type of allocator used to manage memory and elements.
|
||||
*/
|
||||
template<typename Ret, typename... Args, typename Allocator>
|
||||
class sink<sigh<Ret(Args...), Allocator>> {
|
||||
using signal_type = sigh<Ret(Args...), Allocator>;
|
||||
using delegate_type = signal_type::delegate_type;
|
||||
using difference_type = signal_type::container_type::difference_type;
|
||||
|
||||
template<auto Candidate, typename Type>
|
||||
static void release(Type value_or_instance, void *signal) {
|
||||
sink{*static_cast<signal_type *>(signal)}.disconnect<Candidate>(value_or_instance);
|
||||
}
|
||||
|
||||
template<auto Candidate>
|
||||
static void release(void *signal) {
|
||||
sink{*static_cast<signal_type *>(signal)}.disconnect<Candidate>();
|
||||
}
|
||||
|
||||
template<typename Func>
|
||||
void disconnect_if(Func callback) {
|
||||
auto &ref = signal_or_assert();
|
||||
|
||||
for(auto pos = ref.calls.size(); pos; --pos) {
|
||||
if(auto &elem = ref.calls[pos - 1u]; callback(elem)) {
|
||||
elem = stl::move(ref.calls.back());
|
||||
ref.calls.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] auto &signal_or_assert() const noexcept {
|
||||
ENTT_ASSERT(signal != nullptr, "Invalid pointer to signal");
|
||||
return *signal;
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Constructs an invalid sink. */
|
||||
sink() noexcept
|
||||
: signal{} {}
|
||||
|
||||
/**
|
||||
* @brief Constructs a sink that is allowed to modify a given signal.
|
||||
* @param ref A valid reference to a signal object.
|
||||
*/
|
||||
sink(sigh<Ret(Args...), Allocator> &ref) noexcept
|
||||
: signal{&ref} {}
|
||||
|
||||
/**
|
||||
* @brief Returns false if at least a listener is connected to the sink.
|
||||
* @return True if the sink has no listeners connected, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool empty() const noexcept {
|
||||
return signal_or_assert().calls.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a free function or an unbound member to a signal.
|
||||
* @tparam Candidate Function or member to connect to the signal.
|
||||
* @return A properly initialized connection object.
|
||||
*/
|
||||
template<auto Candidate>
|
||||
connection connect() {
|
||||
disconnect<Candidate>();
|
||||
|
||||
delegate_type call{};
|
||||
call.template connect<Candidate>();
|
||||
signal_or_assert().calls.push_back(stl::move(call));
|
||||
|
||||
delegate<void(void *)> conn{};
|
||||
conn.template connect<&release<Candidate>>();
|
||||
return {conn, signal};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a free function with payload or a bound member to a
|
||||
* signal.
|
||||
*
|
||||
* The signal isn't responsible for the connected object or the payload.
|
||||
* Users must always guarantee that the lifetime of the instance overcomes
|
||||
* the one of the signal.<br/>
|
||||
* When used to connect a free function with payload, its signature must be
|
||||
* such that the instance is the first argument before the ones used to
|
||||
* define the signal itself.
|
||||
*
|
||||
* @tparam Candidate Function or member to connect to the signal.
|
||||
* @tparam Type Type of class or type of payload.
|
||||
* @param value_or_instance A valid reference that fits the purpose.
|
||||
* @return A properly initialized connection object.
|
||||
*/
|
||||
template<auto Candidate, typename Type>
|
||||
connection connect(Type &value_or_instance) {
|
||||
disconnect<Candidate>(value_or_instance);
|
||||
|
||||
delegate_type call{};
|
||||
call.template connect<Candidate>(value_or_instance);
|
||||
signal_or_assert().calls.push_back(stl::move(call));
|
||||
|
||||
delegate<void(void *)> conn{};
|
||||
conn.template connect<&release<Candidate, Type &>>(value_or_instance);
|
||||
return {conn, signal};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a free function with payload or a bound member to a
|
||||
* signal.
|
||||
*
|
||||
* @sa connect(Type &)
|
||||
*
|
||||
* @tparam Candidate Function or member to connect to the signal.
|
||||
* @tparam Type Type of class or type of payload.
|
||||
* @param value_or_instance A valid pointer that fits the purpose.
|
||||
* @return A properly initialized connection object.
|
||||
*/
|
||||
template<auto Candidate, typename Type>
|
||||
connection connect(Type *value_or_instance) {
|
||||
disconnect<Candidate>(value_or_instance);
|
||||
|
||||
delegate_type call{};
|
||||
call.template connect<Candidate>(value_or_instance);
|
||||
signal_or_assert().calls.push_back(stl::move(call));
|
||||
|
||||
delegate<void(void *)> conn{};
|
||||
conn.template connect<&release<Candidate, Type *>>(value_or_instance);
|
||||
return {conn, signal};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects a free function or an unbound member from a signal.
|
||||
* @tparam Candidate Function or member to disconnect from the signal.
|
||||
*/
|
||||
template<auto Candidate>
|
||||
void disconnect() {
|
||||
delegate_type call{};
|
||||
call.template connect<Candidate>();
|
||||
disconnect_if([&call](const auto &elem) { return elem == call; });
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects a free function with payload or a bound member from a
|
||||
* signal.
|
||||
*
|
||||
* The signal isn't responsible for the connected object or the payload.
|
||||
* Users must always guarantee that the lifetime of the instance overcomes
|
||||
* the one of the signal.<br/>
|
||||
* When used to connect a free function with payload, its signature must be
|
||||
* such that the instance is the first argument before the ones used to
|
||||
* define the signal itself.
|
||||
*
|
||||
* @tparam Candidate Function or member to disconnect from the signal.
|
||||
* @tparam Type Type of class or type of payload, if any.
|
||||
* @param value_or_instance A valid reference that fits the purpose.
|
||||
*/
|
||||
template<auto Candidate, typename Type>
|
||||
void disconnect(Type &value_or_instance) {
|
||||
delegate_type call{};
|
||||
call.template connect<Candidate>(value_or_instance);
|
||||
disconnect_if([&call](const auto &elem) { return elem == call; });
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects a free function with payload or a bound member from a
|
||||
* signal.
|
||||
*
|
||||
* @sa disconnect(Type &)
|
||||
*
|
||||
* @tparam Candidate Function or member to disconnect from the signal.
|
||||
* @tparam Type Type of class or type of payload, if any.
|
||||
* @param value_or_instance A valid pointer that fits the purpose.
|
||||
*/
|
||||
template<auto Candidate, typename Type>
|
||||
void disconnect(Type *value_or_instance) {
|
||||
delegate_type call{};
|
||||
call.template connect<Candidate>(value_or_instance);
|
||||
disconnect_if([&call](const auto &elem) { return elem == call; });
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects free functions with payload or bound members from a
|
||||
* signal.
|
||||
* @param value_or_instance A valid object that fits the purpose.
|
||||
*/
|
||||
void disconnect(const void *value_or_instance) {
|
||||
ENTT_ASSERT(value_or_instance != nullptr, "Invalid value or instance");
|
||||
disconnect_if([value_or_instance](const auto &elem) { return elem.data() == value_or_instance; });
|
||||
}
|
||||
|
||||
/*! @brief Disconnects all the listeners from a signal. */
|
||||
void disconnect() {
|
||||
signal_or_assert().calls.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns true if a sink is correctly initialized, false otherwise.
|
||||
* @return True if a sink is correctly initialized, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] explicit operator bool() const noexcept {
|
||||
return signal != nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
signal_type *signal;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Deduction guide.
|
||||
*
|
||||
* It allows to deduce the signal handler type of a sink directly from the
|
||||
* signal it refers to.
|
||||
*
|
||||
* @tparam Ret Return type of a function type.
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
* @tparam Allocator Type of allocator used to manage memory and elements.
|
||||
*/
|
||||
template<typename Ret, typename... Args, typename Allocator>
|
||||
sink(sigh<Ret(Args...), Allocator> &) -> sink<sigh<Ret(Args...), Allocator>>;
|
||||
|
||||
} // namespace entt
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user