From a01a0cd7b3a3f21d2780c3e549c384f8910e221d Mon Sep 17 00:00:00 2001 From: Zakarum Date: Fri, 21 Jun 2024 23:12:32 +0200 Subject: [PATCH] Fix badges and docs --- .github/workflows/badge.yml | 2 +- Cargo.toml | 6 +- README.md | 312 ++++++++++++++++++++++++++++-------- proc/Cargo.toml | 2 +- src/lib.rs | 2 +- 5 files changed, 249 insertions(+), 75 deletions(-) diff --git a/.github/workflows/badge.yml b/.github/workflows/badge.yml index 988f8df..b0665c2 100644 --- a/.github/workflows/badge.yml +++ b/.github/workflows/badge.yml @@ -2,7 +2,7 @@ name: badge on: push: - branches: [ master ] + branches: [ main ] env: CARGO_TERM_COLOR: always diff --git a/Cargo.toml b/Cargo.toml index 2d4db76..a2b27b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ members = ["proc-lib", "proc"] [workspace.package] -version = "1.0.0-rc1" +version = "1.0.0-rc2" edition = "2021" authors = ["Zakarum "] license = "MIT OR Apache-2.0" @@ -10,7 +10,7 @@ documentation = "https://docs.rs/edict" homepage = "https://github.com/zakarumych/edict" repository = "https://github.com/zakarumych/edict" readme = "README.md" -description = "Experimental entity-component-system library" +description = "Powerful entity-component-system library" keywords = ["ecs", "entity"] categories = ["no-std", "game-development", "data-structures"] @@ -43,7 +43,7 @@ default = ["std", "scheduler", "flow"] rayon = ["dep:rayon", "std"] [dependencies] -edict-proc = { version = "1.0.0-rc1", path = "proc" } +edict-proc = { version = "=1.0.0-rc2", path = "proc" } amity = { version = "0.2.1", default-features = false, features = ["alloc"] } hashbrown = { version = "0.14" } smallvec = { version = "1.10", features = ["union"], default-features = false } diff --git a/README.md b/README.md index 05e1af9..f18148a 100644 --- a/README.md +++ b/README.md @@ -3,98 +3,272 @@ [![docs](https://img.shields.io/badge/docs.rs-edict-66c2a5?style=for-the-badge&labelColor=555555&logoColor=white)](https://docs.rs/edict) [![actions](https://img.shields.io/github/actions/workflow/status/zakarumych/edict/badge.yml?branch=master&style=for-the-badge)](https://github.com/zakarumych/edict/actions/workflows/badge.yml) [![MIT/Apache](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?style=for-the-badge)](COPYING) -![loc](https://img.shields.io/tokei/lines/github/zakarumych/edict?style=for-the-badge) - -## Edict +![loc](https://img.shields.io/endpoint?url=https://ghloc.vercel.app/api/zakarumych/edict/badge?filter=.py$,.scss$,.rs$&style=for-the-badge&logoColor=white&label=Lines%20of%20Code) Edict is a fast and powerful ECS crate that expands traditional ECS feature set. Written in Rust by your fellow 🦀 -### Features +# Features + +### General purpose + +Archetype based ECS with fast iteration and ergonomics in mind. + +### Simple IDs + +[`EntityId`] as a unique identifier of an entity. +Edict uses unique IDs without generation and recycling. +This greatly simplifying serialization of the [`World`]'s state as it doesn't require any processing of entity IDs. + +IDs are allocated in sequence from [`IdRange`]s that are allocated by [`IdRangeAllocator`]. +By default [`IdRange`] that spans from 1 to `u64::MAX` is used. +Custom [`IdRangeAllocator`] can be provided to [`WorldBuilder`] to use custom ID ranges. + +For example in client-server architecture, server and client may use non-overlapping ID ranges. +Thus allowing state serialized on server to be transferred to client without ID mapping, +which can be cumbersome when components reference entities. + +In multi-server or p2p architecture [`IdRangeAllocator`] would need to communicate to allocate disjoint ID ranges for each server. + +### Ergonomic entity types + +[`Entity`] and [`AliveEntity`] traits implemented for entity types. +Entity types provide convenient guaranties for entity existence and its location. + +[`EntityId`] implements only [`Entity`] as it doesn't provide any guaranties. + +[`EntityBound`] is guaranteed to be alive, allowing using it in methods that doesn't handle entity absence. +Using it with wrong [`World`] may cause panic. + +[`EntityLoc`] not only guarantees entity existence but also provides location of the entity in the archetypes, +allowing to skip lookup step when accessing its components. +Using it with wrong [`World`] may cause panic. + +[`EntityRef`] is special. +It doesn't implement [`Entity`] or [`AliveEntity`] traits since it should be used in world methods. +Instead it provides direct access to entity's data and allows mutations such as inserting/removing components. + +### Flexible queries + +Powerful [`Query`] mechanism that can filter entities by components, relations and other criteria and fetch entity data. +Queries can be mutable or immutable, sendable or non-sendable, stateful or stateless. +Using query on [`World`] creates [`View`]s that can be used to iterate over entities that match the query yielding query items. + +### Resources + +Built-in type-map for singleton values called "resources". +Resources can be inserted into/fetched from [`World`]. +Resources live separately from entities and their components. + +### Non-thread-safe types + +Support for [`!Send`] and [`!Sync`] components and resources with some limitations. + +[`World`] itself is not sendable but shareable between threads via [`WorldShare`] wrapper. +Thread owning [`World`] is referred as "main" thread. + +Components and resources that are [`!Send`] can be fetched mutably only from "main" thread. +Components and resources that are [`!Sync`] can be fetched immutably only from "main" thread. +Since reference to [`World`] may exist outside "main" thread, [`WorldLocal`] reference should be used, +it can be created using mutable reference to [`World`]. + +### Automatic change tracking. + +Each component instance is equipped with epoch counter that tracks last potential mutation of the component. +Queries may read and update components epoch to track changes. +Queries to filter recently changed components are provided with [`Modified`] type. +Last epoch can be obtained with [`World::epoch`]. + +### Entity relations + +A relation can be added to pair of entities, binding them together. +Queries may fetch relations and filter entities by their relations to other entities. +When either of the two entities is despawned, relation is dropped. +[`Relation`] type may further configure behavior of the bounded entities. + +### Runtime and compile time checks -* General purpose archetype based ECS with fast iteration. +Runtime checks for query validity and mutable aliasing avoidance. +[`ViewCell`] with runtime checks allows multiple views with aliased access coexist, +deferring checks to runtime that prevents invalid aliasing to occur. -* Relations can be added to pair of entities, binding them together. - When either of the two entities is despawned, relation is dropped. - [`Relation`] type may further configure behavior of the bonds. +When this is not required, [`View`]s with compile time checks should be used instead. -* Change tracking. - Each component instance is equipped with epoch counter that tracks last potential mutation of the component. - Special query type uses epoch counter to skip entities where component wasn't changed since specified epoch. - Last epoch can be obtained with [`World::epoch`]. +### Deferred actions -* Built-in type-map for singleton values called "resources". - Resources can be inserted into/fetched from [`World`]. - Resources live separately from entities and their components. +Use [`ActionEncoder`] for recording actions and run them later with mutable access to [`World`]. +Or [`LocalActionEncoder`] instead when action is not `Send`. +Or convenient [`WorldLocal::defer*`] methods to defer actions to internal [`LocalActionEncoder`]. -* Runtime checks for query validity and mutable aliasing avoidance. - This requires atomic operations at the beginning iteration on next archetype. +### Customizable -* Support for [`!Send`] and [`!Sync`] components. - [`!Send`] components cannot be fetched mutably from outside "main" thread. - [`!Sync`] components cannot be fetched immutably from outside "main" thread. - [`World`] has to be [`!Send`] but implements [`Sync`]. +[`WorldBuilder`] provides opportunity to override some behavior. +See below for details. -* [`ActionEncoder`] allows recording actions and later run them on [`World`]. - Actions get mutable access to [`World`]. +### Components with trait and without -* Component replace/drop hooks. - Components can define hooks that will be executed on value drop and replace. - Hooks can read old and new values, [`EntityId`] and can record actions into [`ActionEncoder`]. +Optional [`Component`] trait that allows implicit component type registration when component is inserted first time. +Implicit registration uses behavior defined by [`Component`] implementation as-is. +When needed, explicit registration can be done using [`WorldBuilder`] to override component behavior. -* Component type may define a set of types that can be borrowed from it. - Borrowed type may be not sized, allowing slices, dyn traits and any other [`!Sized`] types. - There's macro to define dyn trait borrows. - Special kind of queries look into possible borrows to fetch. +Non [`Component`] types require explicit registration and +few methods with `_external` suffix is used with them instead of normal ones. +Only default registration is possible when [`World`] is already built. +When needed, explicit registration can be done using [`WorldBuilder`] to override component behavior. -* [`WorldBuilder`] can be used to manually register component types and override default behavior. +### Hooks -* Optional [`Component`] trait to allow implicit component type registration by insertion methods. - Implicit registration uses behavior defined by [`Component`] implementation as-is. - Separate insertions methods with [`Component`] trait bound lifted can be used where trait is not implemented or implementation is not visible for generic type. - Those methods require pre-registration of the component type. If type was not registered - method panics. - Both explicit registration with [`WorldBuilder`] and implicit registration via insertion method with [`Component`] type bound is enough. +Component replace/drop hooks are called automatically when component is replaced or dropped. -* [`System`] trait and [`IntoSystem`] implemented for functions if argument types implement [`FnArg`]. - This way practically any system can be defined as a function. +When component is registered it can be equipped with hooks to be called when component value is replaced or dropped. +Implicit registration of [`Component`] types will register hooks defined on the trait impl. -* [`Scheduler`] that can run [`System`]s in parallel using provided executor. +Drop hook is called when component is dropped via `World::drop` or entity is despawned and is not +called when component is removed from entity. -* Coroutines support. - Edict provides [`flow`] module that allows spawning coroutines that can access [`World`] when they are running. - Coroutines can be spawned attached to an entity in which case they will be cancelled automatically when polled if entity is despawned. - Edict intentionally doesn't provide any low-level futures that will do the waiting, - this is left to the user or other libraries (engine) to implement. - [`Flows`] instance is the executor type that takes care of collecting and running coroutines. - Simply call [`Flows::execute`] each tick using the same [`World`] instance. +Replace hook is called when component is replaced e.g. component is inserted into entity +and entity already has component of the same type. +Replace hook returns boolean value that indicates if drop hook should be called for replaced component. -### no_std support +Hooks can record actions into provided [`LocalActionEncoder`] that will be executed +before [`World`] method that caused the hook to be called returns. -`edict` can be used in `no_std` environment but requires `alloc`. -With `"std"` feature enabled error types implement `Error` trait. -`"std"` feature is enabled by default and must be turned off for `no_std` environment. -Dependent crates that also support `no_std` should use `default-features = false` for `edict` dependency, -and optionally enable `"std"` if needed. +When component implements [`Component`] trait, hooks defined on the trait impl are registered automatically to call +[`Component::on_drop`] and [`Component::on_replace`] methods. +They may be overridden with custom hooks using [`WorldBuilder`]. +For non [`Component`] types hooks can be registered only via [`WorldBuilder`]. +Default registration with [`World`] will not register any hooks. + +### Borrows + +Component type may define borrowing operations to borrow another type from it. +Borrowed type may be not sized, allowing slices and dyn traits to be borrowed. +A macro to help define borrowing operations is provided. +Queries that tries to borrow type from suitable components are provided: +* [`BorrowAll`] borrows from all components that implement borrowing requested type. + Yields a `Vec` with borrowed values since multiple components of the entity may provide it. + Skips entities if none of the components provide the requested type. +* [`BorrowAny`] borrows from first suitable component that implements borrowing requested type. + Yields a single value. + Skips entities if none of the components provide the requested type. +* [`BorrowOne`] is configured with `TypeId` of component from which it should borrow requested type. + Panics if component doesn't provide the requested type. + Skips entities without the component. + +### Systems + +Systems is convenient way to build logic that operates on [`World`]. +Edict defines [`System`] trait to run logic on [`World`] and [`IntoSystem`] for types convertible to [`System`]. + +Functions may implement [`IntoSystem`] automatically - +it is required to return `()` and accept arguments that implement [`FnArg`] trait. +There are [`FnArg`] implementations for [`View`]s to iterate over entities, +[`Res`] and [`ResMut`], [`ResNoSync`] and [`ResMutNoSend`] to access resources, +[`ActionEncoder`] to record actions that mutate [`World`]'s state and [`State`] to store system's local state between runs. + +### Easy scheduler + +[`Scheduler`] is provided to run [`System`]s. +Systems added to the [`Scheduler`] run in parallel where possible, +however they act **as if** executed sequentially in order they were added. + +If systems do not conflict they may be executed in parallel. + +If systems conflict, the one added first will be executed before the one added later can start. + +`std` threads or `rayon` can be used as an executor. +User may provide custom executor by implementing [`ScopedExecutor`] trait. + +Requires `"scheduler"` feature which is enabled by default. + +### Async + +Futures executor to run logic that requires waiting for certain conditions or events +or otherwise spans for multiple ticks. + +Logic that requires waiting can be complex to implement using systems. +Systems run in loop and usually work on every entity with certain components. +Implementing waiting logic would require adding waiting state to existing or new components and +logic would be spread across many system runs or even many systems. + +Futures may use `await` syntax to wait for certain conditions or events. +Futures that can access ECS data are referred in Edict as "flows". + +Flows can be spawned in the [`World`] using [`World::spawn_flow`] method. +`Flows` type is used as an executor to run spawned flows. + +Flows can be bound to an entity and spawned using [`World::spawn_flow_for`] method, [`EntityRef::spawn_flow`] or [`flow::Entity::spawn_flow`] +Such flows will be cancelled if entity is despawned. + +Due to borrow checker limitations, closures can't be spawned as flows directly, +To work around this issue [`flow_fn!`] macro accepts valid closure syntax and produces a flow that can be spawned. + +User may implement low-level futures using `poll*` methods of [`flow::World`] and [`flow::Entity`] to access taks [`Context`](std::task::Context). +Edict provides only a couple of low-level futures that will do the waiting: +[`yield_now!`] yields control to the executor once and resumes on next execution. + +It is recommended to use flows for high-level logic that spans multiple ticks +and use systems to do low-level logic that runs every tick. +Flows may request systems to perform operations by adding special components to entities. +And systems may spawn flows to do long-running operations. + +Requires `"flow"` feature which is enabled by default. + +# no_std support + +Edict can be used in `no_std` environment but requires `alloc`. +`"std"` feature is enabled by default. + +If "std" feature is disabled, error types will not implement `std::error::Error`. +And "flow" and "scheduler" feature would require extern functions to be provided. -[`Send`]: https://doc.rust-lang.org/std/marker/trait.Send.html [`!Send`]: https://doc.rust-lang.org/std/marker/trait.Send.html -[`Sync`]: https://doc.rust-lang.org/std/marker/trait.Sync.html -[`!Sync`]: https://doc.rust-lang.org/std/marker/trait.Sync.html -[`World`]: https://docs.rs/edict/latest/edict/world/struct.World.html -[`ActionEncoder`]: https://docs.rs/edict/latest/edict/action/struct.ActionEncoder.html -[`EntityId`]: https://docs.rs/edict/latest/edict/entity/struct.EntityId.html [`!Sized`]: https://doc.rust-lang.org/std/marker/trait.Sized.html -[`Component`]: https://docs.rs/edict/latest/edict/component/struct.Component.html -[`World::epoch`]: https://docs.rs/edict/latest/edict/world/struct.World.html#method.epoch -[`Relation`]: https://docs.rs/edict/latest/edict/relation/trait.Relation.html -[`WorldBuilder`]: https://docs.rs/edict/latest/edict/world/struct.WorldBuilder.html -[`System`]: https://docs.rs/edict/latest/edict/system/struct.System.html -[`IntoSystem`]: https://docs.rs/edict/latest/edict/system/struct.IntoSystem.html -[`FnArg`]: https://docs.rs/edict/latest/edict/system/struct.FnArg.html -[`Scheduler`]: https://docs.rs/edict/latest/edict/scheduler/struct.Scheduler.html -[`flow`]: https://docs.rs/edict/latest/edict/flow/index.html -[`Flows`]: https://docs.rs/edict/latest/edict/flow/struct.Flows.html -[`Flows::execute`]: https://docs.rs/edict/latest/edict/flow/struct.Flows.html#method.execute +[`!Sync`]: https://doc.rust-lang.org/std/marker/trait.Sync.html +[`ActionEncoder`]: https://docs.rs/edict/1.0.0-rc1/edict/action/struct.ActionEncoder.html +[`AliveEntity`]: https://docs.rs/edict/1.0.0-rc1/edict/entity/trait.AliveEntity.html +[`BorrowAll`]: https://docs.rs/edict/1.0.0-rc1/edict/query/struct.BorrowAll.html +[`BorrowAny`]: https://docs.rs/edict/1.0.0-rc1/edict/query/struct.BorrowAny.html +[`BorrowOne`]: https://docs.rs/edict/1.0.0-rc1/edict/query/struct.BorrowOne.html +[`Component`]: https://docs.rs/edict/1.0.0-rc1/edict/component/trait.Component.html +[`Component::on_drop`]: https://docs.rs/edict/1.0.0-rc1/edict/component/trait.Component.html#method.on_drop +[`Component::on_replace`]: https://docs.rs/edict/1.0.0-rc1/edict/component/trait.Component.html#method.on_replace +[`Entity`]: https://docs.rs/edict/1.0.0-rc1/edict/entity/trait.Entity.html +[`EntityBound`]: https://docs.rs/edict/1.0.0-rc1/edict/entity/trait.EntityBound.html +[`EntityId`]: https://docs.rs/edict/1.0.0-rc1/edict/entity/struct.EntityId.html +[`EntityLoc`]: https://docs.rs/edict/1.0.0-rc1/edict/entity/struct.EntityLoc.html +[`EntityRef`]: https://docs.rs/edict/1.0.0-rc1/edict/entity/struct.EntityRef.html +[`EntityRef::spawn_flow`]: https://docs.rs/edict/1.0.0-rc1/edict/entity/struct.EntityRef.html#method.spawn_flow +[`flow`]: https://docs.rs/edict/1.0.0-rc1/edict/flow/index.html +[`flow_fn!`]: https://docs.rs/edict/1.0.0-rc1/edict/flow/macro.flow_fn.html +[`Flows`]: https://docs.rs/edict/1.0.0-rc1/edict/flow/struct.Flows.html +[`Flows::execute`]: https://docs.rs/edict/1.0.0-rc1/edict/flow/struct.Flows.html#method.execute +[`FnArg`]: https://docs.rs/edict/1.0.0-rc1/edict/system/trait.FnArg.html +[`IdRange`]: https://docs.rs/edict/1.0.0-rc1/edict/entity/struct.IdRange.html +[`IdRangeAllocator`]: https://docs.rs/edict/1.0.0-rc1/edict/entity/trait.IdRangeAllocator.html +[`IntoSystem`]: https://docs.rs/edict/1.0.0-rc1/edict/system/trait.IntoSystem.html +[`LocalActionEncoder`]: https://docs.rs/edict/1.0.0-rc1/edict/action/struct.LocalActionEncoder.html +[`Modified`]: https://docs.rs/edict/1.0.0-rc1/edict/query/struct.Modified.html +[`Query`]: https://docs.rs/edict/1.0.0-rc1/edict/query/trait.Query.html +[`Relation`]: https://docs.rs/edict/1.0.0-rc1/edict/relation/trait.Relation.html +[`Res`]: https://docs.rs/edict/1.0.0-rc1/edict/resources/struct.Res.html +[`ResMut`]: https://docs.rs/edict/1.0.0-rc1/edict/resources/struct.ResMut.html +[`ResNoSync`]: https://docs.rs/edict/1.0.0-rc1/edict/system/struct.ResNoSync.html +[`ResMutNoSend`]: https://docs.rs/edict/1.0.0-rc1/edict/system/struct.ResMutNoSend.html +[`Scheduler`]: https://docs.rs/edict/1.0.0-rc1/edict/scheduler/struct.Scheduler.html +[`ScopedExecutor`]: https://docs.rs/edict/1.0.0-rc1/edict/executor/trait.ScopedExecutor.html +[`System`]: https://docs.rs/edict/1.0.0-rc1/edict/system/trait.System.html +[`State`]: https://docs.rs/edict/1.0.0-rc1/edict/system/struct.State.html +[`View`]: https://docs.rs/edict/1.0.0-rc1/edict/view/type.View.html +[`ViewCell`]: https://docs.rs/edict/1.0.0-rc1/edict/view/type.ViewCell.html +[`World`]: https://docs.rs/edict/1.0.0-rc1/edict/world/struct.World.html +[`World::epoch`]: https://docs.rs/edict/1.0.0-rc1/edict/world/struct.World.html#method.epoch +[`World::spawn_flow`]: https://docs.rs/edict/1.0.0-rc1/edict/world/struct.World.html#method.spawn_flow +[`World::spawn_flow_for`]: https://docs.rs/edict/1.0.0-rc1/edict/world/struct.World.html#method.spawn_flow_for +[`WorldBuilder`]: https://docs.rs/edict/1.0.0-rc1/edict/world/struct.WorldBuilder.html +[`WorldLocal`]: https://docs.rs/edict/1.0.0-rc1/edict/world/struct.WorldLocal.html +[`WorldLocal::defer*`]: https://docs.rs/edict/1.0.0-rc1/edict/world/struct.WorldLocal.html#method.defer +[`WorldShare`]: https://docs.rs/edict/1.0.0-rc1/edict/world/struct.WorldShare.html ## License diff --git a/proc/Cargo.toml b/proc/Cargo.toml index 3a47429..81d4635 100644 --- a/proc/Cargo.toml +++ b/proc/Cargo.toml @@ -17,4 +17,4 @@ proc-macro = true [dependencies] syn = "2.0" -edict-proc-lib = { version = "1.0.0-rc1", path = "../proc-lib" } +edict-proc-lib = { version = "=1.0.0-rc2", path = "../proc-lib" } diff --git a/src/lib.rs b/src/lib.rs index 0b3f16f..9245850 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -260,8 +260,8 @@ //! [`World::spawn_flow_for`]: crate::world::World::spawn_flow_for //! [`WorldBuilder`]: crate::world::WorldBuilder //! [`WorldLocal`]: crate::world::WorldLocal -//! [`WorldShare`]: crate::world::WorldShare //! [`WorldLocal::defer*`]: crate::world::WorldLocal::defer +//! [`WorldShare`]: crate::world::WorldShare //! #![cfg_attr(not(feature = "std"), no_std)]