jojoleprowebsite

[Done] The Sauce of https://jojolepro.com
git clone https://git.jojolepro.com/jojoleprowebsite.git
Log | Files | Refs | README | LICENSE

commit 4c384afbf802d5b37d47efc2d07a22729a3c03e0
parent 2c601d4f4daf932973ffd210e41087163a3d4359
Author: jojolepro <jojolepro@jojolepro.com>
Date:   Wed, 17 Mar 2021 11:39:56 -0400

fix rss build. rename plank to planck in post.

Diffstat:
Mbuild.sh | 8++++----
Asrc/blog/2021-01-13_planck_ecs/index.txt | 284+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/blog/2021-01-13_planks_ecs/index.txt | 285-------------------------------------------------------------------------------
Asrc/blog/blog.xml | 41+++++++++++++++++++++++++++++++++++++++++
Msrc/blog/index.gmi | 2+-
Msrc/blog/index.html | 2+-
Atarget/gemini/blog/2021-01-13_planck_ecs/index.gmi | 284+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atarget/gemini/blog/2021-01-13_planck_ecs/index.txt | 284+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dtarget/gemini/blog/2021-01-13_planks_ecs/index.gmi | 285-------------------------------------------------------------------------------
Dtarget/gemini/blog/2021-01-13_planks_ecs/index.txt | 285-------------------------------------------------------------------------------
Atarget/gemini/blog/blog.xml | 41+++++++++++++++++++++++++++++++++++++++++
Mtarget/gemini/blog/index.gmi | 2+-
Mtarget/gemini/blog/index.html | 2+-
Atarget/html/blog/2021-01-13_planck_ecs/index.html | 346+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atarget/html/blog/2021-01-13_planck_ecs/index.txt | 284+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dtarget/html/blog/2021-01-13_planks_ecs/index.html | 347-------------------------------------------------------------------------------
Dtarget/html/blog/2021-01-13_planks_ecs/index.txt | 285-------------------------------------------------------------------------------
Atarget/html/blog/blog.xml | 41+++++++++++++++++++++++++++++++++++++++++
Mtarget/html/blog/index.gmi | 2+-
Mtarget/html/blog/index.html | 2+-
20 files changed, 1615 insertions(+), 1497 deletions(-)

diff --git a/build.sh b/build.sh @@ -24,18 +24,18 @@ item="<item> <link>https://www.jojolepro.com/blog/{{link}}</link> </item>" -echo "$rss" > blog/blog.xml +echo "$rss" > src/blog/blog.xml -cat ../src/blog/index.html | grep -v blog.xml | grep -vE "^$" | +cat src/blog/index.html | grep -v blog.xml | grep -vE "^$" | while read -r entry; do title1="${entry##*\">}" title="${title1%%<*}" link1="${entry##*=\"}" link="${link1%%\"*}" - echo "$item" | sed "s/{{title}}/$title/" | sed "s/{{link}}/$link/" >> blog/blog.xml + echo "$item" | sed "s/{{title}}/$title/" | sed "s/{{link}}/$link/" >> src/blog/blog.xml done -echo "$rss_end" >> blog/blog.xml +echo "$rss_end" >> src/blog/blog.xml # HTML compilation diff --git a/src/blog/2021-01-13_planck_ecs/index.txt b/src/blog/2021-01-13_planck_ecs/index.txt @@ -0,0 +1,284 @@ +Planck ECS: A Minimalistic Yet Performant Entity-Component-System Library +================================================================================ + +#``` ++------------------------------------------------------------------------------+ +| "Perfection is achieved, not when there is nothing more to add, | +| but when there is nothing left to take away." | +| - Antoine de Saint-Exupery, 1900 | ++------------------------------------------------------------------------------+ +#``` + +If you already know what an ECS is, jump to the +"Comparison With Other ECS" section. + +The title is a lie. In reality, this is an Entity-Component-Resource-System +library. + +First, let's start at the beginning. +What is an ECS you ask? +An ECS is a way to organise data and modify this data. + +Why not just use regular object oriented code? +For three reasons: +1) Using an ECS is often faster. +2) It uses parallelism to complete the data modifications much faster. +3) It looks much cleaner. + +Good! + +Now, let's cover the basics. + +The Basics +-------------------------------------------------------------------------------- +We have four main elements. + +- Entity: A "thing" that exists in the world. It may be a game character, +a map, a button, anything! By itself, an Entity is a thing with no attributes +at all. Literally, it is just a thing that exists and is nothing. + +- Component: An attribute added to an Entity. This is what defines what the +"thing" really is. + +- Resource: Some data that is not attached to an Entity but also exists. +For example, time is a resource of our world, but isn't specific to any +Entity existing in the world (if we pretend that general relativity isn't a +thing, that is..) + +- System: An operation that transforms Entities, Components and Resources. + +Making It All Come Together +-------------------------------------------------------------------------------- +Here's a quick example of how it looks conceptually: + +Entity 1: +- Name("Button") +- OnClick(Event::ButtonClicked) +- HoverAnimation("assets/button/on_hover.png") +- Position(5, 8) +- Size(10, 2) +- Render(Square, White) + +As you see, the entity is a thing where we "attach" components that +specify what it is. +We read this as: "Entity 1 is a thing with a name 'Button', that creates +an event when clicked, that is animated when hovered, has a physical +position and size and is rendered as a white square." + +Resources: +- Time(current_time) + +System: +- if current_time > 5 seconds, then move all entities' Position left by 3 units. + +We have a simple system that conditionally modifies the Position component +of all entities having one. + +Extra Elements +-------------------------------------------------------------------------------- +To make this all work together, we need some more concepts. + +First, the World. +A World is extremely simplistic: It holds all the entities, components and +resources. +Actually, that's how we used to do it. See, Planck ECS follows the minimalist +mindset. Our World stores only Resources, and everything else has been made a +Resource. Let's see how that works. + +For Entity, we store them in an Entities Resource. Simply a list of existing +entities, with some extra operations to create and kill entities. + +For Component, we store them in a Components<T> Resource. Similar to Entities, +it is a list. The main difference is that you access components using an Entity. + +A good way to think of it, even though it is not implemented this way, +is as the following: +Entities: List(Entity) +Components<T>: HashMap(Entity, T) + +Now, we have a way to contain entities, components and resources: the world. +What are we forgetting? Ah yes, the systems! +Where are they stored? +How do we execute them? +How do they get access to the data in World? + +Systems are stored in a Dispatcher. Dispatchers are built from a list of Systems +and are used to execute Systems either in sequence or in parallel. +The Dispatcher will fetch resources from the World automatically and execute +the System in a way that guarantees there will not be any conflicts while +accessing resources. + +To do this, Systems need to be built in a way that corresponds to what the +Dispatcher can handle. + +Constraints On Systems +-------------------------------------------------------------------------------- +These are the constraints that specify how systems may be built: + +1) Systems must take only references as arguments. + +2) All mutable references must be after all immutable references. +For example: fn my_system(first: &u32, second: &u64, third: &mut u16) +This constraint is attributable to the way traits are implemented for generic +types in rust. Removing this constraint would make the build time factorial, +which would effectively never complete. + +3) Systems must return a SystemResult. This is to gracefully handle and +recover from errors in systems. + +4) System arguments must implement Default. If they don't, then you need to use +&Option<WhatYouWant> instead of directly using &WhatYouWant. +This constraint exists so that resources may be automatically created for you, +as well as enforcing that any resource that might not exist is actually handled +by the system without any issue. + +How It Actually Looks +-------------------------------------------------------------------------------- +Importing the library: +``` +use planck_ecs::*; +``` + +Creating an entity: +``` +let mut entities = Entities::default(); +let entity1 = entities.create(); +let entity2 = entities.create(); +``` + +Creating components: +``` +struct A; +let mut components = Components::default(); +components.insert(entity1, A); +``` + +Creating a world: +``` +let mut world = World::default(); +``` + +Creating a system: +``` +fn my_system(value: &Components<A>) -> SystemResult { + Ok(()) +} +``` + +Creating a system as a closure: +``` +let my_system = |value: &Components<A>| Ok(()); +``` + +Creating and using a dispatcher: +``` +let dispatcher = DispatcherBuilder::default() + .add(my_system) + .build(&mut world); +// Run without parallelism. +dispatcher.run_seq(&mut world).expect("Error in a system!"); +// Run in parallel. +dispatcher.run_par(&mut world).expect("Error in a system!"); + +// Does some cleanup related to deleted entities. +world.maintain(); +``` + +Joining Components +-------------------------------------------------------------------------------- +The last part of the puzzle: How to write the example system from earlier +that modifies the position using the time? + +For this, we need to introduce joining. Joining starts with us specifying +multiple Component types and bitwise conditions. Don't be afraid, this is +simple. Here is an example: +join!(&positions_components && &size_components) + +This will create an iterator going through all entities that have both a +Position component AND a Size component. If you use &mut instead of &, then you +will get a mutable reference to the component in question. + +The join macro supports the following operators: && || ! +Those work as you would expect, with the caveat that operators are strictly read +from left to right. +For example, +join!(&a && &mut b || !&c) +creates an iterator where we only components of entities having the +following are included: they have (an A AND a B) OR do not have a C. +The reference to B will be mutable. + +Finally, when joining, what you get is actually: +(&Option<A>, &mut Option<B>, &Option<C>) + +The options are always present when joining over multiple components. + +Together: +``` +fn position_update_if_time(time: &Time, sizes: &Components<Size>, + positions: &mut Components<Position>) -> SystemResult { + if time.current_time >= 5 { + // Iterate over entities having both position and size, but updates + // only the position component. + for (pos, _) in join!(&mut positions && &size) { + pos.as_mut().unwrap().x -= 3; + } + } + Ok(()) +} +``` + +Comparison With Other ECS +-------------------------------------------------------------------------------- +Let's have a quick and informal comparison with other Rust ECS libraries. + +First, performance: According to the last time we ran benchmarks, we were the +fastest library when iterating over a single component. +For other benchmarks, including multiple component joining, entity creation and +deletion and component insertion, we ranked on average second, behind legion, +but sometimes being faster on some benchmarks. + +https://github.com/jojolepro/ecs_bench_suite/tree/planck + +Code Size: The complete code size of Planck ECS, including tests and benchmarks, +is under 1500 lines. +For comparison, Bevy ECS has 5400 lines of code, Specs has 6800, legion has +13000 and shipyard has 25000. + +SystemResult: As far as we know, we are the only ECS where systems return +errors gracefully in this way. + +Macros: System declaration, in most current ECS, either require a +macro-by-example or a procedural macro to be concise. Here, you declare +systems in a way identical to regular functions. + +Tests: We have high standards for tests. Since our code size is small, +all features and all non-trivial public functions are tested. +We also benchmarked all performance-sensitive code. + +Safety: We use unsafe code only as an absolute last resort. +This shows in the numbers. Here's the count of unsafe code snippets found in +popular ECS libraries: +- Specs: 150 +- Bevy ECS: 157 +- Legion: 264 +- Shipyard: 312 +- Planck ECS: 4 + +The numbers speak for themselves. + +Licensing +-------------------------------------------------------------------------------- +Published under CC0, Planck ECS is under the public domain and available for +free! + +Conclusion +-------------------------------------------------------------------------------- +In conclusion, Planck ECS is not an innovative piece of software. It does +the same thing that the community has been doing for years. It just does it in +a better and more safe way. + +If you like this library, please consider donating on patreon: +https://patreon.com/jojolepro + +https://github.com/jojolepro/planck_ecs +https://docs.rs/planck_ecs diff --git a/src/blog/2021-01-13_planks_ecs/index.txt b/src/blog/2021-01-13_planks_ecs/index.txt @@ -1,285 +0,0 @@ -Plank ECS: A Minimalistic Yet Performant Entity-Component-System Library -================================================================================ - -#``` -+------------------------------------------------------------------------------+ -| "Perfection is achieved, not when there is nothing more to add, | -| but when there is nothing left to take away." | -| - Antoine de Saint-Exupery, 1900 | -+------------------------------------------------------------------------------+ -#``` - -If you already know what an ECS is, jump to the -"Comparison With Other ECS" section. - -The title is a lie. In reality, this is an Entity-Component-Resource-System -library. - -First, let's start at the beginning. -What is an ECS you ask? -An ECS is a way to organise data and modify this data. - -Why not just use regular object oriented code? -For three reasons: -1) Using an ECS is often faster. -2) It uses parallelism to complete the data modifications much faster. -3) It looks much cleaner. - -Good! - -Now, let's cover the basics. - -The Basics --------------------------------------------------------------------------------- -We have four main elements. - -- Entity: A "thing" that exists in the world. It may be a game character, -a map, a button, anything! By itself, an Entity is a thing with no attributes -at all. Literally, it is just a thing that exists and is nothing. - -- Component: An attribute added to an Entity. This is what defines what the -"thing" really is. - -- Resource: Some data that is not attached to an Entity but also exists. -For example, time is a resource of our world, but isn't specific to any -Entity existing in the world (if we pretend that general relativity isn't a -thing, that is..) - -- System: An operation that transforms Entities, Components and Resources. - -Making It All Come Together --------------------------------------------------------------------------------- -Here's a quick example of how it looks conceptually: - -Entity 1: -- Name("Button") -- OnClick(Event::ButtonClicked) -- HoverAnimation("assets/button/on_hover.png") -- Position(5, 8) -- Size(10, 2) -- Render(Square, White) - -As you see, the entity is a thing where we "attach" components that -specify what it is. -We read this as: "Entity 1 is a thing with a name 'Button', that creates -an event when clicked, that is animated when hovered, has a physical -position and size and is rendered as a white square." - -Resources: -- Time(current_time) - -System: -- if current_time > 5 seconds, then move all entities' Position left by 3 units. - -We have a simple system that conditionally modifies the Position component -of all entities having one. - -Extra Elements --------------------------------------------------------------------------------- -To make this all work together, we need some more concepts. - -First, the World. -A World is extremely simplistic: It holds all the entities, components and -resources. -Actually, that's how we used to do it. See, Plank ECS follows the minimalist -mindset. Our World stores only Resources, and everything else has been made a -Resource. Let's see how that works. - -For Entity, we store them in an Entities Resource. Simply a list of existing -entities, with some extra operations to create and kill entities. - -For Component, we store them in a Components<T> Resource. Similar to Entities, -it is a list. The main difference is that you access components using an Entity. - -A good way to think of it, even though it is not implemented this way, -is as the following: -Entities: List(Entity) -Components<T>: HashMap(Entity, T) - -Now, we have a way to contain entities, components and resources: the world. -What are we forgetting? Ah yes, the systems! -Where are they stored? -How do we execute them? -How do they get access to the data in World? - -Systems are stored in a Dispatcher. Dispatchers are built from a list of Systems -and are used to execute Systems either in sequence or in parallel. -The Dispatcher will fetch resources from the World automatically and execute -the System in a way that guarantees there will not be any conflicts while -accessing resources. - -To do this, Systems need to be built in a way that corresponds to what the -Dispatcher can handle. - -Constraints On Systems --------------------------------------------------------------------------------- -These are the constraints that specify how systems may be built: - -1) Systems must take only references as arguments. - -2) All mutable references must be after all immutable references. -For example: fn my_system(first: &u32, second: &u64, third: &mut u16) -This constraint is attributable to the way traits are implemented for generic -types in rust. Removing this constraint would make the build time factorial, -which would effectively never complete. - -3) Systems must return a SystemResult. This is to gracefully handle and -recover from errors in systems. - -4) System arguments must implement Default. If they don't, then you need to use -&Option<WhatYouWant> instead of directly using &WhatYouWant. -This constraint exists so that resources may be automatically created for you, -as well as enforcing that any resource that might not exist is actually handled -by the system without any issue. - -How It Actually Looks --------------------------------------------------------------------------------- -Importing the library: -``` -use plank_ecs::*; -``` - -Creating an entity: -``` -let mut entities = Entities::default(); -let entity1 = entities.create(); -let entity2 = entities.create(); -``` - -Creating components: -``` -struct A; -let mut components = Components::default(); -components.insert(entity1, A); -``` - -Creating a world: -``` -let mut world = World::default(); -``` - -Creating a system: -``` -fn my_system(value: &Components<A>) -> SystemResult { - Ok(()) -} -``` - -Creating a system as a closure: -``` -let my_system = |value: &Components<A>| Ok(()); -``` - -Creating and using a dispatcher: -``` -let dispatcher = DispatcherBuilder::default() - .add(my_system) - .build(&mut world); -// Run without parallelism. -dispatcher.run_seq(&mut world).expect("Error in a system!"); -// Run in parallel. -dispatcher.run_par(&mut world).expect("Error in a system!"); - -// Does some cleanup related to deleted entities. -world.maintain(); -``` - -Joining Components --------------------------------------------------------------------------------- -The last part of the puzzle: How to write the example system from earlier -that modifies the position using the time? - -For this, we need to introduce joining. Joining starts with us specifying -multiple Component types and bitwise conditions. Don't be afraid, this is -simple. Here is an example: -join!(&positions_components && &size_components) - -This will create an iterator going through all entities that have both a -Position component AND a Size component. If you use &mut instead of &, then you -will get a mutable reference to the component in question. - -The join macro supports the following operators: && || ! -Those work as you would expect, with the caveat that operators are strictly read -from left to right. -For example, -join!(&a && &mut b || !&c) -creates an iterator where we only components of entities having the -following are included: they have (an A AND a B) OR do not have a C. -The reference to B will be mutable. - -Finally, when joining, what you get is actually: -(&Option<A>, &mut Option<B>, &Option<C>) - -The options are always present when joining over multiple components. - -Together: -``` -fn position_update_if_time(time: &Time, sizes: &Components<Size>, - positions: &mut Components<Position>) -> SystemResult { - if time.current_time >= 5 { - // Iterate over entities having both position and size, but updates - // only the position component. - for (pos, _) in join!(&mut positions && &size) { - pos.as_mut().unwrap().x -= 3; - } - } - Ok(()) -} -``` - -Comparison With Other ECS --------------------------------------------------------------------------------- -Let's have a quick and informal comparison with other Rust ECS libraries. - -First, performance: According to the last time we ran benchmarks, we were the -fastest library when iterating over a single component. -For other benchmarks, including multiple component joining, entity creation and -deletion and component insertion, we ranked on average second, behind legion, -but sometimes being faster on some benchmarks. - -Code Size: The complete code size of Plank ECS, including tests and benchmarks, -is under 1500 lines. -For comparison, Bevy ECS has 5400 lines of code, Specs has 6800, legion has 13000, shipyard has -25000. - -SystemResult: As far as we know, we are the only ECS where systems return -errors gracefully in this way. - -Macros: System declaration, in most current ECS, either require a -macro-by-example or a procedural macro to be concise. Here, you declare -systems in a way identical to regular functions. - -Tests: We have high standards for tests. Since our code size is small, -all features and all non-trivial public functions are tested. -We also benchmarked all performance-sensitive code. - -Safety: We use unsafe code only as an absolute last resort. -This shows in the numbers. Here's the count of unsafe code snippets found in -popular ECS libraries: -- Specs: 150 -- Bevy ECS: 157 -- Legion: 264 -- Shipyard: 312 -- Plank ECS: 4 - -The numbers speak for themselves. - -Licensing --------------------------------------------------------------------------------- -Because of the quality and the time spent on this library, it was decided -that the AGPL license would be more adequate for it. We want the code to remain -open source and encourage contributions to come back. - -As we know some people want to make use of our product in commercial software, -we offer a paid commercial license as an alternative way to contribute back. - -Conclusion --------------------------------------------------------------------------------- -In conclusion, Plank ECS is not an innovative piece of software. It does -the same thing that the community has been doing for years. It just does it in -a better and more safe way. - -https://git.jojolepro.com/plank_ecs -https://docs.rs/plank_ecs -Part of the Focks Team project. -https://jojolepro.com/focks/ diff --git a/src/blog/blog.xml b/src/blog/blog.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> +<channel> +<title>Jojolepro Blog</title> +<description>The Blog of the Fockses!</description> +<link>https://www.jojolepro.com</link> +<atom:link href="https://www.jojolepro.com/blog/blog.xml" rel="self" type="application/rss+xml"/> +<item> +<title>Removing the US</title> +<link>https://www.jojolepro.com/blog/2021-01-13_removing_the_us</link> +</item> +<item> +<title>Planck: A Minimalistic Yet Performant ECS Library</title> +<link>https://www.jojolepro.com/blog/2021-01-13_planck_ecs</link> +</item> +<item> +<title>The Fall of the Giant</title> +<link>https://www.jojolepro.com/blog/2020-11-17_the_fall_of_the_giant</link> +</item> +<item> +<title>Event Chaining as a Decoupling Method in ECS Game Engines</title> +<link>https://www.jojolepro.com/blog/2020-08-20_event_chaining</link> +</item> +<item> +<title>Minimalist Alternatives to Popular Applications</title> +<link>https://www.jojolepro.com/blog/2020-07-07_minimalist_alternatives</link> +</item> +<item> +<title>Why I Moved Away From Wordpress</title> +<link>https://www.jojolepro.com/blog/2020-05-04_why_i_moved_from_wordpress</link> +</item> +<item> +<title>Simple School Documents</title> +<link>https://www.jojolepro.com/blog/2020-04-09_simple_school_documents</link> +</item> +<item> +<title>Extracting Data From Websites</title> +<link>https://www.jojolepro.com/blog/2020-03-31_extracting_data_from_websites</link> +</item> +</channel> +</rss> diff --git a/src/blog/index.gmi b/src/blog/index.gmi @@ -1,5 +1,5 @@ => 2021-01-13_removing_the_us Removing the US -=> 2021-01-13_planks_ecs Plank: A Minimalistic Yet Performant ECS Library +=> 2021-01-13_planck_ecs Planck: A Minimalistic Yet Performant ECS Library => 2020-11-17_the_fall_of_the_giant The Fall of the Giant => 2020-08-20_event_chaining Event Chaining as a Decoupling Method in ECS Game Engines => 2020-07-07_minimalist_alternatives Minimalist Alternatives to Popular Applications diff --git a/src/blog/index.html b/src/blog/index.html @@ -1,5 +1,5 @@ <a href="2021-01-13_removing_the_us">Removing the US</a> -<a href="2021-01-13_planks_ecs">Plank: A Minimalistic Yet Performant ECS Library</a> +<a href="2021-01-13_planck_ecs">Planck: A Minimalistic Yet Performant ECS Library</a> <a href="2020-11-17_the_fall_of_the_giant">The Fall of the Giant</a> <a href="2020-08-20_event_chaining">Event Chaining as a Decoupling Method in ECS Game Engines</a> <a href="2020-07-07_minimalist_alternatives">Minimalist Alternatives to Popular Applications</a> diff --git a/target/gemini/blog/2021-01-13_planck_ecs/index.gmi b/target/gemini/blog/2021-01-13_planck_ecs/index.gmi @@ -0,0 +1,284 @@ +# Jojolepro +=> blog Blog +=> quotes Quotes +=> projects Projects +=> https://git.jojolepro.com Git +=> https://github.com/jojolepro/ GitHub + + + +## Planck ECS: A Minimalistic Yet Performant Entity-Component-System Library + +### #``` +| "Perfection is achieved, not when there is nothing more to add, | +| but when there is nothing left to take away." | +### | - Antoine de Saint-Exupery, 1900 | +``` + +If you already know what an ECS is, jump to the +"Comparison With Other ECS" section. + +The title is a lie. In reality, this is an Entity-Component-Resource-System +library. + +First, let's start at the beginning. +What is an ECS you ask? +An ECS is a way to organise data and modify this data. + +Why not just use regular object oriented code? +For three reasons: +1) Using an ECS is often faster. +2) It uses parallelism to complete the data modifications much faster. +3) It looks much cleaner. + +Good! + +Now, let's cover the basics. + +### The Basics +We have four main elements. + +* Entity: A "thing" that exists in the world. It may be a game character, +a map, a button, anything! By itself, an Entity is a thing with no attributes +at all. Literally, it is just a thing that exists and is nothing. + +* Component: An attribute added to an Entity. This is what defines what the +"thing" really is. + +* Resource: Some data that is not attached to an Entity but also exists. +For example, time is a resource of our world, but isn't specific to any +Entity existing in the world (if we pretend that general relativity isn't a +thing, that is..) + +* System: An operation that transforms Entities, Components and Resources. + +### Making It All Come Together +Here's a quick example of how it looks conceptually: + +Entity 1: +* Name("Button") +* OnClick(Event::ButtonClicked) +* HoverAnimation("assets/button/on_hover.png") +* Position(5, 8) +* Size(10, 2) +* Render(Square, White) + +As you see, the entity is a thing where we "attach" components that +specify what it is. +We read this as: "Entity 1 is a thing with a name 'Button', that creates +an event when clicked, that is animated when hovered, has a physical +position and size and is rendered as a white square." + +Resources: +* Time(current_time) + +System: +* if current_time > 5 seconds, then move all entities' Position left by 3 units. + +We have a simple system that conditionally modifies the Position component +of all entities having one. + +### Extra Elements +To make this all work together, we need some more concepts. + +First, the World. +A World is extremely simplistic: It holds all the entities, components and +resources. +Actually, that's how we used to do it. See, Planck ECS follows the minimalist +mindset. Our World stores only Resources, and everything else has been made a +Resource. Let's see how that works. + +For Entity, we store them in an Entities Resource. Simply a list of existing +entities, with some extra operations to create and kill entities. + +For Component, we store them in a Components<T> Resource. Similar to Entities, +it is a list. The main difference is that you access components using an Entity. + +A good way to think of it, even though it is not implemented this way, +is as the following: +Entities: List(Entity) +Components<T>: HashMap(Entity, T) + +Now, we have a way to contain entities, components and resources: the world. +What are we forgetting? Ah yes, the systems! +Where are they stored? +How do we execute them? +How do they get access to the data in World? + +Systems are stored in a Dispatcher. Dispatchers are built from a list of Systems +and are used to execute Systems either in sequence or in parallel. +The Dispatcher will fetch resources from the World automatically and execute +the System in a way that guarantees there will not be any conflicts while +accessing resources. + +To do this, Systems need to be built in a way that corresponds to what the +Dispatcher can handle. + +### Constraints On Systems +These are the constraints that specify how systems may be built: + +1) Systems must take only references as arguments. + +2) All mutable references must be after all immutable references. +For example: fn my_system(first: &u32, second: &u64, third: &mut u16) +This constraint is attributable to the way traits are implemented for generic +types in rust. Removing this constraint would make the build time factorial, +which would effectively never complete. + +3) Systems must return a SystemResult. This is to gracefully handle and +recover from errors in systems. + +4) System arguments must implement Default. If they don't, then you need to use +&Option<WhatYouWant> instead of directly using &WhatYouWant. +This constraint exists so that resources may be automatically created for you, +as well as enforcing that any resource that might not exist is actually handled +by the system without any issue. + +### How It Actually Looks +Importing the library: +``` +use planck_ecs::*; +``` + +Creating an entity: +``` +let mut entities = Entities::default(); +let entity1 = entities.create(); +let entity2 = entities.create(); +``` + +Creating components: +``` +struct A; +let mut components = Components::default(); +components.insert(entity1, A); +``` + +Creating a world: +``` +let mut world = World::default(); +``` + +Creating a system: +``` +fn my_system(value: &Components<A>) -> SystemResult { + Ok(()) +} +``` + +Creating a system as a closure: +``` +let my_system = |value: &Components<A>| Ok(()); +``` + +Creating and using a dispatcher: +``` +let dispatcher = DispatcherBuilder::default() + .add(my_system) + .build(&mut world); +// Run without parallelism. +dispatcher.run_seq(&mut world).expect("Error in a system!"); +// Run in parallel. +dispatcher.run_par(&mut world).expect("Error in a system!"); + +// Does some cleanup related to deleted entities. +world.maintain(); +``` + +### Joining Components +The last part of the puzzle: How to write the example system from earlier +that modifies the position using the time? + +For this, we need to introduce joining. Joining starts with us specifying +multiple Component types and bitwise conditions. Don't be afraid, this is +simple. Here is an example: +join!(&positions_components && &size_components) + +This will create an iterator going through all entities that have both a +Position component AND a Size component. If you use &mut instead of &, then you +will get a mutable reference to the component in question. + +The join macro supports the following operators: && || ! +Those work as you would expect, with the caveat that operators are strictly read +from left to right. +For example, +join!(&a && &mut b || !&c) +creates an iterator where we only components of entities having the +following are included: they have (an A AND a B) OR do not have a C. +The reference to B will be mutable. + +Finally, when joining, what you get is actually: +(&Option<A>, &mut Option<B>, &Option<C>) + +The options are always present when joining over multiple components. + +Together: +``` +fn position_update_if_time(time: &Time, sizes: &Components<Size>, + positions: &mut Components<Position>) -> SystemResult { + if time.current_time >= 5 { + // Iterate over entities having both position and size, but updates + // only the position component. + for (pos, _) in join!(&mut positions && &size) { + pos.as_mut().unwrap().x -= 3; + } + } + Ok(()) +} +``` + +### Comparison With Other ECS +Let's have a quick and informal comparison with other Rust ECS libraries. + +First, performance: According to the last time we ran benchmarks, we were the +fastest library when iterating over a single component. +For other benchmarks, including multiple component joining, entity creation and +deletion and component insertion, we ranked on average second, behind legion, +but sometimes being faster on some benchmarks. + +=>https://github.com/jojolepro/ecs_bench_suite/tree/planck + +Code Size: The complete code size of Planck ECS, including tests and benchmarks, +is under 1500 lines. +For comparison, Bevy ECS has 5400 lines of code, Specs has 6800, legion has +13000 and shipyard has 25000. + +SystemResult: As far as we know, we are the only ECS where systems return +errors gracefully in this way. + +Macros: System declaration, in most current ECS, either require a +macro-by-example or a procedural macro to be concise. Here, you declare +systems in a way identical to regular functions. + +Tests: We have high standards for tests. Since our code size is small, +all features and all non-trivial public functions are tested. +We also benchmarked all performance-sensitive code. + +Safety: We use unsafe code only as an absolute last resort. +This shows in the numbers. Here's the count of unsafe code snippets found in +popular ECS libraries: +* Specs: 150 +* Bevy ECS: 157 +* Legion: 264 +* Shipyard: 312 +* Planck ECS: 4 + +The numbers speak for themselves. + +### Licensing +Published under CC0, Planck ECS is under the public domain and available for +free! + +### Conclusion +In conclusion, Planck ECS is not an innovative piece of software. It does +the same thing that the community has been doing for years. It just does it in +a better and more safe way. + +If you like this library, please consider donating on patreon: +=>https://patreon.com/jojolepro + +=>https://github.com/jojolepro/planck_ecs +=>https://docs.rs/planck_ecs + +(C) Joël Lupien 2020-2021 +=>/blog/2021-01-13_planck_ecs/index.txt View page source diff --git a/target/gemini/blog/2021-01-13_planck_ecs/index.txt b/target/gemini/blog/2021-01-13_planck_ecs/index.txt @@ -0,0 +1,284 @@ +Planck ECS: A Minimalistic Yet Performant Entity-Component-System Library +================================================================================ + +#``` ++------------------------------------------------------------------------------+ +| "Perfection is achieved, not when there is nothing more to add, | +| but when there is nothing left to take away." | +| - Antoine de Saint-Exupery, 1900 | ++------------------------------------------------------------------------------+ +#``` + +If you already know what an ECS is, jump to the +"Comparison With Other ECS" section. + +The title is a lie. In reality, this is an Entity-Component-Resource-System +library. + +First, let's start at the beginning. +What is an ECS you ask? +An ECS is a way to organise data and modify this data. + +Why not just use regular object oriented code? +For three reasons: +1) Using an ECS is often faster. +2) It uses parallelism to complete the data modifications much faster. +3) It looks much cleaner. + +Good! + +Now, let's cover the basics. + +The Basics +-------------------------------------------------------------------------------- +We have four main elements. + +- Entity: A "thing" that exists in the world. It may be a game character, +a map, a button, anything! By itself, an Entity is a thing with no attributes +at all. Literally, it is just a thing that exists and is nothing. + +- Component: An attribute added to an Entity. This is what defines what the +"thing" really is. + +- Resource: Some data that is not attached to an Entity but also exists. +For example, time is a resource of our world, but isn't specific to any +Entity existing in the world (if we pretend that general relativity isn't a +thing, that is..) + +- System: An operation that transforms Entities, Components and Resources. + +Making It All Come Together +-------------------------------------------------------------------------------- +Here's a quick example of how it looks conceptually: + +Entity 1: +- Name("Button") +- OnClick(Event::ButtonClicked) +- HoverAnimation("assets/button/on_hover.png") +- Position(5, 8) +- Size(10, 2) +- Render(Square, White) + +As you see, the entity is a thing where we "attach" components that +specify what it is. +We read this as: "Entity 1 is a thing with a name 'Button', that creates +an event when clicked, that is animated when hovered, has a physical +position and size and is rendered as a white square." + +Resources: +- Time(current_time) + +System: +- if current_time > 5 seconds, then move all entities' Position left by 3 units. + +We have a simple system that conditionally modifies the Position component +of all entities having one. + +Extra Elements +-------------------------------------------------------------------------------- +To make this all work together, we need some more concepts. + +First, the World. +A World is extremely simplistic: It holds all the entities, components and +resources. +Actually, that's how we used to do it. See, Planck ECS follows the minimalist +mindset. Our World stores only Resources, and everything else has been made a +Resource. Let's see how that works. + +For Entity, we store them in an Entities Resource. Simply a list of existing +entities, with some extra operations to create and kill entities. + +For Component, we store them in a Components<T> Resource. Similar to Entities, +it is a list. The main difference is that you access components using an Entity. + +A good way to think of it, even though it is not implemented this way, +is as the following: +Entities: List(Entity) +Components<T>: HashMap(Entity, T) + +Now, we have a way to contain entities, components and resources: the world. +What are we forgetting? Ah yes, the systems! +Where are they stored? +How do we execute them? +How do they get access to the data in World? + +Systems are stored in a Dispatcher. Dispatchers are built from a list of Systems +and are used to execute Systems either in sequence or in parallel. +The Dispatcher will fetch resources from the World automatically and execute +the System in a way that guarantees there will not be any conflicts while +accessing resources. + +To do this, Systems need to be built in a way that corresponds to what the +Dispatcher can handle. + +Constraints On Systems +-------------------------------------------------------------------------------- +These are the constraints that specify how systems may be built: + +1) Systems must take only references as arguments. + +2) All mutable references must be after all immutable references. +For example: fn my_system(first: &u32, second: &u64, third: &mut u16) +This constraint is attributable to the way traits are implemented for generic +types in rust. Removing this constraint would make the build time factorial, +which would effectively never complete. + +3) Systems must return a SystemResult. This is to gracefully handle and +recover from errors in systems. + +4) System arguments must implement Default. If they don't, then you need to use +&Option<WhatYouWant> instead of directly using &WhatYouWant. +This constraint exists so that resources may be automatically created for you, +as well as enforcing that any resource that might not exist is actually handled +by the system without any issue. + +How It Actually Looks +-------------------------------------------------------------------------------- +Importing the library: +``` +use planck_ecs::*; +``` + +Creating an entity: +``` +let mut entities = Entities::default(); +let entity1 = entities.create(); +let entity2 = entities.create(); +``` + +Creating components: +``` +struct A; +let mut components = Components::default(); +components.insert(entity1, A); +``` + +Creating a world: +``` +let mut world = World::default(); +``` + +Creating a system: +``` +fn my_system(value: &Components<A>) -> SystemResult { + Ok(()) +} +``` + +Creating a system as a closure: +``` +let my_system = |value: &Components<A>| Ok(()); +``` + +Creating and using a dispatcher: +``` +let dispatcher = DispatcherBuilder::default() + .add(my_system) + .build(&mut world); +// Run without parallelism. +dispatcher.run_seq(&mut world).expect("Error in a system!"); +// Run in parallel. +dispatcher.run_par(&mut world).expect("Error in a system!"); + +// Does some cleanup related to deleted entities. +world.maintain(); +``` + +Joining Components +-------------------------------------------------------------------------------- +The last part of the puzzle: How to write the example system from earlier +that modifies the position using the time? + +For this, we need to introduce joining. Joining starts with us specifying +multiple Component types and bitwise conditions. Don't be afraid, this is +simple. Here is an example: +join!(&positions_components && &size_components) + +This will create an iterator going through all entities that have both a +Position component AND a Size component. If you use &mut instead of &, then you +will get a mutable reference to the component in question. + +The join macro supports the following operators: && || ! +Those work as you would expect, with the caveat that operators are strictly read +from left to right. +For example, +join!(&a && &mut b || !&c) +creates an iterator where we only components of entities having the +following are included: they have (an A AND a B) OR do not have a C. +The reference to B will be mutable. + +Finally, when joining, what you get is actually: +(&Option<A>, &mut Option<B>, &Option<C>) + +The options are always present when joining over multiple components. + +Together: +``` +fn position_update_if_time(time: &Time, sizes: &Components<Size>, + positions: &mut Components<Position>) -> SystemResult { + if time.current_time >= 5 { + // Iterate over entities having both position and size, but updates + // only the position component. + for (pos, _) in join!(&mut positions && &size) { + pos.as_mut().unwrap().x -= 3; + } + } + Ok(()) +} +``` + +Comparison With Other ECS +-------------------------------------------------------------------------------- +Let's have a quick and informal comparison with other Rust ECS libraries. + +First, performance: According to the last time we ran benchmarks, we were the +fastest library when iterating over a single component. +For other benchmarks, including multiple component joining, entity creation and +deletion and component insertion, we ranked on average second, behind legion, +but sometimes being faster on some benchmarks. + +https://github.com/jojolepro/ecs_bench_suite/tree/planck + +Code Size: The complete code size of Planck ECS, including tests and benchmarks, +is under 1500 lines. +For comparison, Bevy ECS has 5400 lines of code, Specs has 6800, legion has +13000 and shipyard has 25000. + +SystemResult: As far as we know, we are the only ECS where systems return +errors gracefully in this way. + +Macros: System declaration, in most current ECS, either require a +macro-by-example or a procedural macro to be concise. Here, you declare +systems in a way identical to regular functions. + +Tests: We have high standards for tests. Since our code size is small, +all features and all non-trivial public functions are tested. +We also benchmarked all performance-sensitive code. + +Safety: We use unsafe code only as an absolute last resort. +This shows in the numbers. Here's the count of unsafe code snippets found in +popular ECS libraries: +- Specs: 150 +- Bevy ECS: 157 +- Legion: 264 +- Shipyard: 312 +- Planck ECS: 4 + +The numbers speak for themselves. + +Licensing +-------------------------------------------------------------------------------- +Published under CC0, Planck ECS is under the public domain and available for +free! + +Conclusion +-------------------------------------------------------------------------------- +In conclusion, Planck ECS is not an innovative piece of software. It does +the same thing that the community has been doing for years. It just does it in +a better and more safe way. + +If you like this library, please consider donating on patreon: +https://patreon.com/jojolepro + +https://github.com/jojolepro/planck_ecs +https://docs.rs/planck_ecs diff --git a/target/gemini/blog/2021-01-13_planks_ecs/index.gmi b/target/gemini/blog/2021-01-13_planks_ecs/index.gmi @@ -1,285 +0,0 @@ -# Jojolepro -=> blog Blog -=> quotes Quotes -=> projects Projects -=> https://git.jojolepro.com Git -=> https://github.com/jojolepro/ GitHub - - - -## Plank ECS: A Minimalistic Yet Performant Entity-Component-System Library - -### #``` -| "Perfection is achieved, not when there is nothing more to add, | -| but when there is nothing left to take away." | -### | - Antoine de Saint-Exupery, 1900 | -``` - -If you already know what an ECS is, jump to the -"Comparison With Other ECS" section. - -The title is a lie. In reality, this is an Entity-Component-Resource-System -library. - -First, let's start at the beginning. -What is an ECS you ask? -An ECS is a way to organise data and modify this data. - -Why not just use regular object oriented code? -For three reasons: -1) Using an ECS is often faster. -2) It uses parallelism to complete the data modifications much faster. -3) It looks much cleaner. - -Good! - -Now, let's cover the basics. - -### The Basics -We have four main elements. - -* Entity: A "thing" that exists in the world. It may be a game character, -a map, a button, anything! By itself, an Entity is a thing with no attributes -at all. Literally, it is just a thing that exists and is nothing. - -* Component: An attribute added to an Entity. This is what defines what the -"thing" really is. - -* Resource: Some data that is not attached to an Entity but also exists. -For example, time is a resource of our world, but isn't specific to any -Entity existing in the world (if we pretend that general relativity isn't a -thing, that is..) - -* System: An operation that transforms Entities, Components and Resources. - -### Making It All Come Together -Here's a quick example of how it looks conceptually: - -Entity 1: -* Name("Button") -* OnClick(Event::ButtonClicked) -* HoverAnimation("assets/button/on_hover.png") -* Position(5, 8) -* Size(10, 2) -* Render(Square, White) - -As you see, the entity is a thing where we "attach" components that -specify what it is. -We read this as: "Entity 1 is a thing with a name 'Button', that creates -an event when clicked, that is animated when hovered, has a physical -position and size and is rendered as a white square." - -Resources: -* Time(current_time) - -System: -* if current_time > 5 seconds, then move all entities' Position left by 3 units. - -We have a simple system that conditionally modifies the Position component -of all entities having one. - -### Extra Elements -To make this all work together, we need some more concepts. - -First, the World. -A World is extremely simplistic: It holds all the entities, components and -resources. -Actually, that's how we used to do it. See, Plank ECS follows the minimalist -mindset. Our World stores only Resources, and everything else has been made a -Resource. Let's see how that works. - -For Entity, we store them in an Entities Resource. Simply a list of existing -entities, with some extra operations to create and kill entities. - -For Component, we store them in a Components<T> Resource. Similar to Entities, -it is a list. The main difference is that you access components using an Entity. - -A good way to think of it, even though it is not implemented this way, -is as the following: -Entities: List(Entity) -Components<T>: HashMap(Entity, T) - -Now, we have a way to contain entities, components and resources: the world. -What are we forgetting? Ah yes, the systems! -Where are they stored? -How do we execute them? -How do they get access to the data in World? - -Systems are stored in a Dispatcher. Dispatchers are built from a list of Systems -and are used to execute Systems either in sequence or in parallel. -The Dispatcher will fetch resources from the World automatically and execute -the System in a way that guarantees there will not be any conflicts while -accessing resources. - -To do this, Systems need to be built in a way that corresponds to what the -Dispatcher can handle. - -### Constraints On Systems -These are the constraints that specify how systems may be built: - -1) Systems must take only references as arguments. - -2) All mutable references must be after all immutable references. -For example: fn my_system(first: &u32, second: &u64, third: &mut u16) -This constraint is attributable to the way traits are implemented for generic -types in rust. Removing this constraint would make the build time factorial, -which would effectively never complete. - -3) Systems must return a SystemResult. This is to gracefully handle and -recover from errors in systems. - -4) System arguments must implement Default. If they don't, then you need to use -&Option<WhatYouWant> instead of directly using &WhatYouWant. -This constraint exists so that resources may be automatically created for you, -as well as enforcing that any resource that might not exist is actually handled -by the system without any issue. - -### How It Actually Looks -Importing the library: -``` -use plank_ecs::*; -``` - -Creating an entity: -``` -let mut entities = Entities::default(); -let entity1 = entities.create(); -let entity2 = entities.create(); -``` - -Creating components: -``` -struct A; -let mut components = Components::default(); -components.insert(entity1, A); -``` - -Creating a world: -``` -let mut world = World::default(); -``` - -Creating a system: -``` -fn my_system(value: &Components<A>) -> SystemResult { - Ok(()) -} -``` - -Creating a system as a closure: -``` -let my_system = |value: &Components<A>| Ok(()); -``` - -Creating and using a dispatcher: -``` -let dispatcher = DispatcherBuilder::default() - .add(my_system) - .build(&mut world); -// Run without parallelism. -dispatcher.run_seq(&mut world).expect("Error in a system!"); -// Run in parallel. -dispatcher.run_par(&mut world).expect("Error in a system!"); - -// Does some cleanup related to deleted entities. -world.maintain(); -``` - -### Joining Components -The last part of the puzzle: How to write the example system from earlier -that modifies the position using the time? - -For this, we need to introduce joining. Joining starts with us specifying -multiple Component types and bitwise conditions. Don't be afraid, this is -simple. Here is an example: -join!(&positions_components && &size_components) - -This will create an iterator going through all entities that have both a -Position component AND a Size component. If you use &mut instead of &, then you -will get a mutable reference to the component in question. - -The join macro supports the following operators: && || ! -Those work as you would expect, with the caveat that operators are strictly read -from left to right. -For example, -join!(&a && &mut b || !&c) -creates an iterator where we only components of entities having the -following are included: they have (an A AND a B) OR do not have a C. -The reference to B will be mutable. - -Finally, when joining, what you get is actually: -(&Option<A>, &mut Option<B>, &Option<C>) - -The options are always present when joining over multiple components. - -Together: -``` -fn position_update_if_time(time: &Time, sizes: &Components<Size>, - positions: &mut Components<Position>) -> SystemResult { - if time.current_time >= 5 { - // Iterate over entities having both position and size, but updates - // only the position component. - for (pos, _) in join!(&mut positions && &size) { - pos.as_mut().unwrap().x -= 3; - } - } - Ok(()) -} -``` - -### Comparison With Other ECS -Let's have a quick and informal comparison with other Rust ECS libraries. - -First, performance: According to the last time we ran benchmarks, we were the -fastest library when iterating over a single component. -For other benchmarks, including multiple component joining, entity creation and -deletion and component insertion, we ranked on average second, behind legion, -but sometimes being faster on some benchmarks. - -Code Size: The complete code size of Plank ECS, including tests and benchmarks, -is under 1500 lines. -For comparison, Bevy ECS has 5400 lines of code, Specs has 6800, legion has 13000, shipyard has -25000. - -SystemResult: As far as we know, we are the only ECS where systems return -errors gracefully in this way. - -Macros: System declaration, in most current ECS, either require a -macro-by-example or a procedural macro to be concise. Here, you declare -systems in a way identical to regular functions. - -Tests: We have high standards for tests. Since our code size is small, -all features and all non-trivial public functions are tested. -We also benchmarked all performance-sensitive code. - -Safety: We use unsafe code only as an absolute last resort. -This shows in the numbers. Here's the count of unsafe code snippets found in -popular ECS libraries: -* Specs: 150 -* Bevy ECS: 157 -* Legion: 264 -* Shipyard: 312 -* Plank ECS: 4 - -The numbers speak for themselves. - -### Licensing -Because of the quality and the time spent on this library, it was decided -that the AGPL license would be more adequate for it. We want the code to remain -open source and encourage contributions to come back. - -As we know some people want to make use of our product in commercial software, -we offer a paid commercial license as an alternative way to contribute back. - -### Conclusion -In conclusion, Plank ECS is not an innovative piece of software. It does -the same thing that the community has been doing for years. It just does it in -a better and more safe way. - -=>https://git.jojolepro.com/plank_ecs -=>https://docs.rs/plank_ecs -Part of the Focks Team project. -=>https://jojolepro.com/focks/ - -(C) Joël Lupien 2020-2021 -=>/blog/2021-01-13_planks_ecs/index.txt View page source diff --git a/target/gemini/blog/2021-01-13_planks_ecs/index.txt b/target/gemini/blog/2021-01-13_planks_ecs/index.txt @@ -1,285 +0,0 @@ -Plank ECS: A Minimalistic Yet Performant Entity-Component-System Library -================================================================================ - -#``` -+------------------------------------------------------------------------------+ -| "Perfection is achieved, not when there is nothing more to add, | -| but when there is nothing left to take away." | -| - Antoine de Saint-Exupery, 1900 | -+------------------------------------------------------------------------------+ -#``` - -If you already know what an ECS is, jump to the -"Comparison With Other ECS" section. - -The title is a lie. In reality, this is an Entity-Component-Resource-System -library. - -First, let's start at the beginning. -What is an ECS you ask? -An ECS is a way to organise data and modify this data. - -Why not just use regular object oriented code? -For three reasons: -1) Using an ECS is often faster. -2) It uses parallelism to complete the data modifications much faster. -3) It looks much cleaner. - -Good! - -Now, let's cover the basics. - -The Basics --------------------------------------------------------------------------------- -We have four main elements. - -- Entity: A "thing" that exists in the world. It may be a game character, -a map, a button, anything! By itself, an Entity is a thing with no attributes -at all. Literally, it is just a thing that exists and is nothing. - -- Component: An attribute added to an Entity. This is what defines what the -"thing" really is. - -- Resource: Some data that is not attached to an Entity but also exists. -For example, time is a resource of our world, but isn't specific to any -Entity existing in the world (if we pretend that general relativity isn't a -thing, that is..) - -- System: An operation that transforms Entities, Components and Resources. - -Making It All Come Together --------------------------------------------------------------------------------- -Here's a quick example of how it looks conceptually: - -Entity 1: -- Name("Button") -- OnClick(Event::ButtonClicked) -- HoverAnimation("assets/button/on_hover.png") -- Position(5, 8) -- Size(10, 2) -- Render(Square, White) - -As you see, the entity is a thing where we "attach" components that -specify what it is. -We read this as: "Entity 1 is a thing with a name 'Button', that creates -an event when clicked, that is animated when hovered, has a physical -position and size and is rendered as a white square." - -Resources: -- Time(current_time) - -System: -- if current_time > 5 seconds, then move all entities' Position left by 3 units. - -We have a simple system that conditionally modifies the Position component -of all entities having one. - -Extra Elements --------------------------------------------------------------------------------- -To make this all work together, we need some more concepts. - -First, the World. -A World is extremely simplistic: It holds all the entities, components and -resources. -Actually, that's how we used to do it. See, Plank ECS follows the minimalist -mindset. Our World stores only Resources, and everything else has been made a -Resource. Let's see how that works. - -For Entity, we store them in an Entities Resource. Simply a list of existing -entities, with some extra operations to create and kill entities. - -For Component, we store them in a Components<T> Resource. Similar to Entities, -it is a list. The main difference is that you access components using an Entity. - -A good way to think of it, even though it is not implemented this way, -is as the following: -Entities: List(Entity) -Components<T>: HashMap(Entity, T) - -Now, we have a way to contain entities, components and resources: the world. -What are we forgetting? Ah yes, the systems! -Where are they stored? -How do we execute them? -How do they get access to the data in World? - -Systems are stored in a Dispatcher. Dispatchers are built from a list of Systems -and are used to execute Systems either in sequence or in parallel. -The Dispatcher will fetch resources from the World automatically and execute -the System in a way that guarantees there will not be any conflicts while -accessing resources. - -To do this, Systems need to be built in a way that corresponds to what the -Dispatcher can handle. - -Constraints On Systems --------------------------------------------------------------------------------- -These are the constraints that specify how systems may be built: - -1) Systems must take only references as arguments. - -2) All mutable references must be after all immutable references. -For example: fn my_system(first: &u32, second: &u64, third: &mut u16) -This constraint is attributable to the way traits are implemented for generic -types in rust. Removing this constraint would make the build time factorial, -which would effectively never complete. - -3) Systems must return a SystemResult. This is to gracefully handle and -recover from errors in systems. - -4) System arguments must implement Default. If they don't, then you need to use -&Option<WhatYouWant> instead of directly using &WhatYouWant. -This constraint exists so that resources may be automatically created for you, -as well as enforcing that any resource that might not exist is actually handled -by the system without any issue. - -How It Actually Looks --------------------------------------------------------------------------------- -Importing the library: -``` -use plank_ecs::*; -``` - -Creating an entity: -``` -let mut entities = Entities::default(); -let entity1 = entities.create(); -let entity2 = entities.create(); -``` - -Creating components: -``` -struct A; -let mut components = Components::default(); -components.insert(entity1, A); -``` - -Creating a world: -``` -let mut world = World::default(); -``` - -Creating a system: -``` -fn my_system(value: &Components<A>) -> SystemResult { - Ok(()) -} -``` - -Creating a system as a closure: -``` -let my_system = |value: &Components<A>| Ok(()); -``` - -Creating and using a dispatcher: -``` -let dispatcher = DispatcherBuilder::default() - .add(my_system) - .build(&mut world); -// Run without parallelism. -dispatcher.run_seq(&mut world).expect("Error in a system!"); -// Run in parallel. -dispatcher.run_par(&mut world).expect("Error in a system!"); - -// Does some cleanup related to deleted entities. -world.maintain(); -``` - -Joining Components --------------------------------------------------------------------------------- -The last part of the puzzle: How to write the example system from earlier -that modifies the position using the time? - -For this, we need to introduce joining. Joining starts with us specifying -multiple Component types and bitwise conditions. Don't be afraid, this is -simple. Here is an example: -join!(&positions_components && &size_components) - -This will create an iterator going through all entities that have both a -Position component AND a Size component. If you use &mut instead of &, then you -will get a mutable reference to the component in question. - -The join macro supports the following operators: && || ! -Those work as you would expect, with the caveat that operators are strictly read -from left to right. -For example, -join!(&a && &mut b || !&c) -creates an iterator where we only components of entities having the -following are included: they have (an A AND a B) OR do not have a C. -The reference to B will be mutable. - -Finally, when joining, what you get is actually: -(&Option<A>, &mut Option<B>, &Option<C>) - -The options are always present when joining over multiple components. - -Together: -``` -fn position_update_if_time(time: &Time, sizes: &Components<Size>, - positions: &mut Components<Position>) -> SystemResult { - if time.current_time >= 5 { - // Iterate over entities having both position and size, but updates - // only the position component. - for (pos, _) in join!(&mut positions && &size) { - pos.as_mut().unwrap().x -= 3; - } - } - Ok(()) -} -``` - -Comparison With Other ECS --------------------------------------------------------------------------------- -Let's have a quick and informal comparison with other Rust ECS libraries. - -First, performance: According to the last time we ran benchmarks, we were the -fastest library when iterating over a single component. -For other benchmarks, including multiple component joining, entity creation and -deletion and component insertion, we ranked on average second, behind legion, -but sometimes being faster on some benchmarks. - -Code Size: The complete code size of Plank ECS, including tests and benchmarks, -is under 1500 lines. -For comparison, Bevy ECS has 5400 lines of code, Specs has 6800, legion has 13000, shipyard has -25000. - -SystemResult: As far as we know, we are the only ECS where systems return -errors gracefully in this way. - -Macros: System declaration, in most current ECS, either require a -macro-by-example or a procedural macro to be concise. Here, you declare -systems in a way identical to regular functions. - -Tests: We have high standards for tests. Since our code size is small, -all features and all non-trivial public functions are tested. -We also benchmarked all performance-sensitive code. - -Safety: We use unsafe code only as an absolute last resort. -This shows in the numbers. Here's the count of unsafe code snippets found in -popular ECS libraries: -- Specs: 150 -- Bevy ECS: 157 -- Legion: 264 -- Shipyard: 312 -- Plank ECS: 4 - -The numbers speak for themselves. - -Licensing --------------------------------------------------------------------------------- -Because of the quality and the time spent on this library, it was decided -that the AGPL license would be more adequate for it. We want the code to remain -open source and encourage contributions to come back. - -As we know some people want to make use of our product in commercial software, -we offer a paid commercial license as an alternative way to contribute back. - -Conclusion --------------------------------------------------------------------------------- -In conclusion, Plank ECS is not an innovative piece of software. It does -the same thing that the community has been doing for years. It just does it in -a better and more safe way. - -https://git.jojolepro.com/plank_ecs -https://docs.rs/plank_ecs -Part of the Focks Team project. -https://jojolepro.com/focks/ diff --git a/target/gemini/blog/blog.xml b/target/gemini/blog/blog.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> +<channel> +<title>Jojolepro Blog</title> +<description>The Blog of the Fockses!</description> +<link>https://www.jojolepro.com</link> +<atom:link href="https://www.jojolepro.com/blog/blog.xml" rel="self" type="application/rss+xml"/> +<item> +<title>Removing the US</title> +<link>https://www.jojolepro.com/blog/2021-01-13_removing_the_us</link> +</item> +<item> +<title>Planck: A Minimalistic Yet Performant ECS Library</title> +<link>https://www.jojolepro.com/blog/2021-01-13_planck_ecs</link> +</item> +<item> +<title>The Fall of the Giant</title> +<link>https://www.jojolepro.com/blog/2020-11-17_the_fall_of_the_giant</link> +</item> +<item> +<title>Event Chaining as a Decoupling Method in ECS Game Engines</title> +<link>https://www.jojolepro.com/blog/2020-08-20_event_chaining</link> +</item> +<item> +<title>Minimalist Alternatives to Popular Applications</title> +<link>https://www.jojolepro.com/blog/2020-07-07_minimalist_alternatives</link> +</item> +<item> +<title>Why I Moved Away From Wordpress</title> +<link>https://www.jojolepro.com/blog/2020-05-04_why_i_moved_from_wordpress</link> +</item> +<item> +<title>Simple School Documents</title> +<link>https://www.jojolepro.com/blog/2020-04-09_simple_school_documents</link> +</item> +<item> +<title>Extracting Data From Websites</title> +<link>https://www.jojolepro.com/blog/2020-03-31_extracting_data_from_websites</link> +</item> +</channel> +</rss> diff --git a/target/gemini/blog/index.gmi b/target/gemini/blog/index.gmi @@ -1,5 +1,5 @@ => 2021-01-13_removing_the_us Removing the US -=> 2021-01-13_planks_ecs Plank: A Minimalistic Yet Performant ECS Library +=> 2021-01-13_planck_ecs Planck: A Minimalistic Yet Performant ECS Library => 2020-11-17_the_fall_of_the_giant The Fall of the Giant => 2020-08-20_event_chaining Event Chaining as a Decoupling Method in ECS Game Engines => 2020-07-07_minimalist_alternatives Minimalist Alternatives to Popular Applications diff --git a/target/gemini/blog/index.html b/target/gemini/blog/index.html @@ -1,5 +1,5 @@ <a href="2021-01-13_removing_the_us">Removing the US</a> -<a href="2021-01-13_planks_ecs">Plank: A Minimalistic Yet Performant ECS Library</a> +<a href="2021-01-13_planck_ecs">Planck: A Minimalistic Yet Performant ECS Library</a> <a href="2020-11-17_the_fall_of_the_giant">The Fall of the Giant</a> <a href="2020-08-20_event_chaining">Event Chaining as a Decoupling Method in ECS Game Engines</a> <a href="2020-07-07_minimalist_alternatives">Minimalist Alternatives to Popular Applications</a> diff --git a/target/html/blog/2021-01-13_planck_ecs/index.html b/target/html/blog/2021-01-13_planck_ecs/index.html @@ -0,0 +1,346 @@ +<!doctype html> +<html lang=en> + <head> + <link href='data:image/png;base64,R0lGODlhGAAYAOeIAAAAALVSwLlUxLxXyLlZxMlb1cpc1hScoRSepNBf3c9h29Rg4NJh39Nh4BWhps9j3NJi3s1k2dVh4tBk3NVi4tNj39Zi49di49di5NBl3NVj4tNk39hi5dZj49Zj5Nli5dRk4ddj5NVk4thj5dti5tNl4Nhj5tZk49Fm3tlj5dRl4Nlj5tdk49dk5Npj5ddk5dVl4dhk5dZl49lk5tdl5dpk5tZm49Nn5NVn4tVn5dZn49Ro4Nlm5tNo5ddn5NBq29Vo4dVo4haortln5dRp4Ndo49Zp49Nq5Raqr9pp59hr5NFu5Nls5s1y5BevtBawtcp25BizuRi1uhm4vi20wbqG4hm7wRq8whm9w7yI4raM4Bq/xRq/xrSO4RrByBrDyRvDyhvFyxvGzBvHzRvHzqeb4BvIzhvIzxzIzxvJz6ad4CTH0aKf3xzK0BzK0RzL0p6i3xzN1BzP1R3P1pGr3h3Q15Or3RzR2CPP2R3R2B3R2R/R2RzS2R3S2SfP2Yex3Yey3TPN2TbN2Xm43DDP2Xa63EXK2kbK2v///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////yH+B2hhaSBoYWkAIfkEAQoA/wAsAAAAABgAGAAACP4A/wkcSLCgwYMFASgEgLDhQiFWrkxZ2JAgACl58vTZuDGPwor/ALzhyFEMmI1uGCIEUKfPHABkODoAMGZjHZUJ02wEEAUARwBofNrEOVBoH5EAzuyMA0Djxi9EAXD5mWdOS5JYo2Ld+pNjGpUAEGwVmrLPlqMkwSLhCgAqAgAHjG40wxCAE656ADzBQwWAHz5ZQ2LhashOlR4uSOToAkjQz5BesB5ic4QHiyQyiFiokQJKoZ8ATnIctCQGBCYqRHjYoWPIihUvyhA6CiDMxj10aFhI8MBGhghBZnTg8fp1FkIKa/aBk2LFhQUlbOAoooHDiRbFX2tR2EbPnw+vR0NMMAJEAQwQFBpoCDFCgoCFAOQEapJ9Q5EKDFCUKPKDAPyid6hhQnZK+GDAAPARVdQaN7yGQQEB/AdSSAlOaOGFAgUEADs=' + rel=icon> + <title>Jojolepro.com</title> + <meta charset=utf-8> + <meta name=description content="A bunch of stuff and things!"> + <style> + body{ + text-align:center; + overflow-y:scroll; + /*font:calc(0.75em + 1vmin) monospace;*/ + font: 1.3em monospace; + background-color: #181a1b; + border-color: #575757; + color: #e8e6e3; + } + pre{ + text-align:left; + display:inline-block; + } + img{ + max-width:80ch; + display:block; + height:auto; + width:100%; + } + nav a { + margin-right: 1ch; + } + a { + color: #3391ff; + } + * { + scrollbar-color: #2a2c2e #1c1e1f; + } + </style> + </head> + <body> + <nav> + <a href=/><b>Jojolepro</b></a> + <br/> + <a href=/blog>Blog</a> + <a href=/quotes>Quotes</a> + <a href=/projects>Projects</a> + <!--<a href=/focks>Focks</a>--> + <a href=https://git.jojolepro.com>Git</a> + <a href=https://github.com/jojolepro/>GitHub</a> + </nav> + <br/> + <article> + <pre> +Planck ECS: A Minimalistic Yet Performant Entity-Component-System Library +================================================================================ + ++------------------------------------------------------------------------------+ +| "Perfection is achieved, not when there is nothing more to add, | +| but when there is nothing left to take away." | +| - Antoine de Saint-Exupery, 1900 | ++------------------------------------------------------------------------------+ + +If you already know what an ECS is, jump to the +"Comparison With Other ECS" section. + +The title is a lie. In reality, this is an Entity-Component-Resource-System +library. + +First, let's start at the beginning. +What is an ECS you ask? +An ECS is a way to organise data and modify this data. + +Why not just use regular object oriented code? +For three reasons: +1) Using an ECS is often faster. +2) It uses parallelism to complete the data modifications much faster. +3) It looks much cleaner. + +Good! + +Now, let's cover the basics. + +The Basics +-------------------------------------------------------------------------------- +We have four main elements. + +- Entity: A "thing" that exists in the world. It may be a game character, +a map, a button, anything! By itself, an Entity is a thing with no attributes +at all. Literally, it is just a thing that exists and is nothing. + +- Component: An attribute added to an Entity. This is what defines what the +"thing" really is. + +- Resource: Some data that is not attached to an Entity but also exists. +For example, time is a resource of our world, but isn't specific to any +Entity existing in the world (if we pretend that general relativity isn't a +thing, that is..) + +- System: An operation that transforms Entities, Components and Resources. + +Making It All Come Together +-------------------------------------------------------------------------------- +Here's a quick example of how it looks conceptually: + +Entity 1: +- Name("Button") +- OnClick(Event::ButtonClicked) +- HoverAnimation("assets/button/on_hover.png") +- Position(5, 8) +- Size(10, 2) +- Render(Square, White) + +As you see, the entity is a thing where we "attach" components that +specify what it is. +We read this as: "Entity 1 is a thing with a name 'Button', that creates +an event when clicked, that is animated when hovered, has a physical +position and size and is rendered as a white square." + +Resources: +- Time(current_time) + +System: +- if current_time &gt; 5 seconds, then move all entities' Position left by 3 units. + +We have a simple system that conditionally modifies the Position component +of all entities having one. + +Extra Elements +-------------------------------------------------------------------------------- +To make this all work together, we need some more concepts. + +First, the World. +A World is extremely simplistic: It holds all the entities, components and +resources. +Actually, that's how we used to do it. See, Planck ECS follows the minimalist +mindset. Our World stores only Resources, and everything else has been made a +Resource. Let's see how that works. + +For Entity, we store them in an Entities Resource. Simply a list of existing +entities, with some extra operations to create and kill entities. + +For Component, we store them in a Components&lt;T&gt; Resource. Similar to Entities, +it is a list. The main difference is that you access components using an Entity. + +A good way to think of it, even though it is not implemented this way, +is as the following: +Entities: List(Entity) +Components&lt;T&gt;: HashMap(Entity, T) + +Now, we have a way to contain entities, components and resources: the world. +What are we forgetting? Ah yes, the systems! +Where are they stored? +How do we execute them? +How do they get access to the data in World? + +Systems are stored in a Dispatcher. Dispatchers are built from a list of Systems +and are used to execute Systems either in sequence or in parallel. +The Dispatcher will fetch resources from the World automatically and execute +the System in a way that guarantees there will not be any conflicts while +accessing resources. + +To do this, Systems need to be built in a way that corresponds to what the +Dispatcher can handle. + +Constraints On Systems +-------------------------------------------------------------------------------- +These are the constraints that specify how systems may be built: + +1) Systems must take only references as arguments. + +2) All mutable references must be after all immutable references. +For example: fn my_system(first: &amp;u32, second: &amp;u64, third: &amp;mut u16) +This constraint is attributable to the way traits are implemented for generic +types in rust. Removing this constraint would make the build time factorial, +which would effectively never complete. + +3) Systems must return a SystemResult. This is to gracefully handle and +recover from errors in systems. + +4) System arguments must implement Default. If they don't, then you need to use +&amp;Option&lt;WhatYouWant&gt; instead of directly using &amp;WhatYouWant. +This constraint exists so that resources may be automatically created for you, +as well as enforcing that any resource that might not exist is actually handled +by the system without any issue. + +How It Actually Looks +-------------------------------------------------------------------------------- +Importing the library: +``` +use planck_ecs::*; +``` + +Creating an entity: +``` +let mut entities = Entities::default(); +let entity1 = entities.create(); +let entity2 = entities.create(); +``` + +Creating components: +``` +struct A; +let mut components = Components::default(); +components.insert(entity1, A); +``` + +Creating a world: +``` +let mut world = World::default(); +``` + +Creating a system: +``` +fn my_system(value: &amp;Components&lt;A&gt;) -&gt; SystemResult { + Ok(()) +} +``` + +Creating a system as a closure: +``` +let my_system = |value: &amp;Components&lt;A&gt;| Ok(()); +``` + +Creating and using a dispatcher: +``` +let dispatcher = DispatcherBuilder::default() + .add(my_system) + .build(&amp;mut world); +// Run without parallelism. +dispatcher.run_seq(&amp;mut world).expect("Error in a system!"); +// Run in parallel. +dispatcher.run_par(&amp;mut world).expect("Error in a system!"); + +// Does some cleanup related to deleted entities. +world.maintain(); +``` + +Joining Components +-------------------------------------------------------------------------------- +The last part of the puzzle: How to write the example system from earlier +that modifies the position using the time? + +For this, we need to introduce joining. Joining starts with us specifying +multiple Component types and bitwise conditions. Don't be afraid, this is +simple. Here is an example: +join!(&amp;positions_components &amp;&amp; &amp;size_components) + +This will create an iterator going through all entities that have both a +Position component AND a Size component. If you use &amp;mut instead of &amp;, then you +will get a mutable reference to the component in question. + +The join macro supports the following operators: &amp;&amp; || ! +Those work as you would expect, with the caveat that operators are strictly read +from left to right. +For example, +join!(&amp;a &amp;&amp; &amp;mut b || !&amp;c) +creates an iterator where we only components of entities having the +following are included: they have (an A AND a B) OR do not have a C. +The reference to B will be mutable. + +Finally, when joining, what you get is actually: +(&amp;Option&lt;A&gt;, &amp;mut Option&lt;B&gt;, &amp;Option&lt;C&gt;) + +The options are always present when joining over multiple components. + +Together: +``` +fn position_update_if_time(time: &amp;Time, sizes: &amp;Components&lt;Size&gt;, + positions: &amp;mut Components&lt;Position&gt;) -&gt; SystemResult { + if time.current_time &gt;= 5 { + // Iterate over entities having both position and size, but updates + // only the position component. + for (pos, _) in join!(&amp;mut positions &amp;&amp; &amp;size) { + pos.as_mut().unwrap().x -= 3; + } + } + Ok(()) +} +``` + +Comparison With Other ECS +-------------------------------------------------------------------------------- +Let's have a quick and informal comparison with other Rust ECS libraries. + +First, performance: According to the last time we ran benchmarks, we were the +fastest library when iterating over a single component. +For other benchmarks, including multiple component joining, entity creation and +deletion and component insertion, we ranked on average second, behind legion, +but sometimes being faster on some benchmarks. + +<a href='https://github.com/jojolepro/ecs_bench_suite/tree/planck'>https://github.com/jojolepro/ecs_bench_suite/tree/planck</a> + +Code Size: The complete code size of Planck ECS, including tests and benchmarks, +is under 1500 lines. +For comparison, Bevy ECS has 5400 lines of code, Specs has 6800, legion has +13000 and shipyard has 25000. + +SystemResult: As far as we know, we are the only ECS where systems return +errors gracefully in this way. + +Macros: System declaration, in most current ECS, either require a +macro-by-example or a procedural macro to be concise. Here, you declare +systems in a way identical to regular functions. + +Tests: We have high standards for tests. Since our code size is small, +all features and all non-trivial public functions are tested. +We also benchmarked all performance-sensitive code. + +Safety: We use unsafe code only as an absolute last resort. +This shows in the numbers. Here's the count of unsafe code snippets found in +popular ECS libraries: +- Specs: 150 +- Bevy ECS: 157 +- Legion: 264 +- Shipyard: 312 +- Planck ECS: 4 + +The numbers speak for themselves. + +Licensing +-------------------------------------------------------------------------------- +Published under CC0, Planck ECS is under the public domain and available for +free! + +Conclusion +-------------------------------------------------------------------------------- +In conclusion, Planck ECS is not an innovative piece of software. It does +the same thing that the community has been doing for years. It just does it in +a better and more safe way. + +If you like this library, please consider donating on patreon: +<a href='https://patreon.com/jojolepro'>https://patreon.com/jojolepro</a> + +<a href='https://github.com/jojolepro/planck_ecs'>https://github.com/jojolepro/planck_ecs</a> +<a href='https://docs.rs/planck_ecs'>https://docs.rs/planck_ecs</a> + </pre> + </article> + <br/> + <footer> + <div> + (C) Joël Lupien 2020-2021 + </div> + <a href="/blog/2021-01-13_planck_ecs/index.txt">View page source</a> + </footer> + </body> +</html> diff --git a/target/html/blog/2021-01-13_planck_ecs/index.txt b/target/html/blog/2021-01-13_planck_ecs/index.txt @@ -0,0 +1,284 @@ +Planck ECS: A Minimalistic Yet Performant Entity-Component-System Library +================================================================================ + +#``` ++------------------------------------------------------------------------------+ +| "Perfection is achieved, not when there is nothing more to add, | +| but when there is nothing left to take away." | +| - Antoine de Saint-Exupery, 1900 | ++------------------------------------------------------------------------------+ +#``` + +If you already know what an ECS is, jump to the +"Comparison With Other ECS" section. + +The title is a lie. In reality, this is an Entity-Component-Resource-System +library. + +First, let's start at the beginning. +What is an ECS you ask? +An ECS is a way to organise data and modify this data. + +Why not just use regular object oriented code? +For three reasons: +1) Using an ECS is often faster. +2) It uses parallelism to complete the data modifications much faster. +3) It looks much cleaner. + +Good! + +Now, let's cover the basics. + +The Basics +-------------------------------------------------------------------------------- +We have four main elements. + +- Entity: A "thing" that exists in the world. It may be a game character, +a map, a button, anything! By itself, an Entity is a thing with no attributes +at all. Literally, it is just a thing that exists and is nothing. + +- Component: An attribute added to an Entity. This is what defines what the +"thing" really is. + +- Resource: Some data that is not attached to an Entity but also exists. +For example, time is a resource of our world, but isn't specific to any +Entity existing in the world (if we pretend that general relativity isn't a +thing, that is..) + +- System: An operation that transforms Entities, Components and Resources. + +Making It All Come Together +-------------------------------------------------------------------------------- +Here's a quick example of how it looks conceptually: + +Entity 1: +- Name("Button") +- OnClick(Event::ButtonClicked) +- HoverAnimation("assets/button/on_hover.png") +- Position(5, 8) +- Size(10, 2) +- Render(Square, White) + +As you see, the entity is a thing where we "attach" components that +specify what it is. +We read this as: "Entity 1 is a thing with a name 'Button', that creates +an event when clicked, that is animated when hovered, has a physical +position and size and is rendered as a white square." + +Resources: +- Time(current_time) + +System: +- if current_time > 5 seconds, then move all entities' Position left by 3 units. + +We have a simple system that conditionally modifies the Position component +of all entities having one. + +Extra Elements +-------------------------------------------------------------------------------- +To make this all work together, we need some more concepts. + +First, the World. +A World is extremely simplistic: It holds all the entities, components and +resources. +Actually, that's how we used to do it. See, Planck ECS follows the minimalist +mindset. Our World stores only Resources, and everything else has been made a +Resource. Let's see how that works. + +For Entity, we store them in an Entities Resource. Simply a list of existing +entities, with some extra operations to create and kill entities. + +For Component, we store them in a Components<T> Resource. Similar to Entities, +it is a list. The main difference is that you access components using an Entity. + +A good way to think of it, even though it is not implemented this way, +is as the following: +Entities: List(Entity) +Components<T>: HashMap(Entity, T) + +Now, we have a way to contain entities, components and resources: the world. +What are we forgetting? Ah yes, the systems! +Where are they stored? +How do we execute them? +How do they get access to the data in World? + +Systems are stored in a Dispatcher. Dispatchers are built from a list of Systems +and are used to execute Systems either in sequence or in parallel. +The Dispatcher will fetch resources from the World automatically and execute +the System in a way that guarantees there will not be any conflicts while +accessing resources. + +To do this, Systems need to be built in a way that corresponds to what the +Dispatcher can handle. + +Constraints On Systems +-------------------------------------------------------------------------------- +These are the constraints that specify how systems may be built: + +1) Systems must take only references as arguments. + +2) All mutable references must be after all immutable references. +For example: fn my_system(first: &u32, second: &u64, third: &mut u16) +This constraint is attributable to the way traits are implemented for generic +types in rust. Removing this constraint would make the build time factorial, +which would effectively never complete. + +3) Systems must return a SystemResult. This is to gracefully handle and +recover from errors in systems. + +4) System arguments must implement Default. If they don't, then you need to use +&Option<WhatYouWant> instead of directly using &WhatYouWant. +This constraint exists so that resources may be automatically created for you, +as well as enforcing that any resource that might not exist is actually handled +by the system without any issue. + +How It Actually Looks +-------------------------------------------------------------------------------- +Importing the library: +``` +use planck_ecs::*; +``` + +Creating an entity: +``` +let mut entities = Entities::default(); +let entity1 = entities.create(); +let entity2 = entities.create(); +``` + +Creating components: +``` +struct A; +let mut components = Components::default(); +components.insert(entity1, A); +``` + +Creating a world: +``` +let mut world = World::default(); +``` + +Creating a system: +``` +fn my_system(value: &Components<A>) -> SystemResult { + Ok(()) +} +``` + +Creating a system as a closure: +``` +let my_system = |value: &Components<A>| Ok(()); +``` + +Creating and using a dispatcher: +``` +let dispatcher = DispatcherBuilder::default() + .add(my_system) + .build(&mut world); +// Run without parallelism. +dispatcher.run_seq(&mut world).expect("Error in a system!"); +// Run in parallel. +dispatcher.run_par(&mut world).expect("Error in a system!"); + +// Does some cleanup related to deleted entities. +world.maintain(); +``` + +Joining Components +-------------------------------------------------------------------------------- +The last part of the puzzle: How to write the example system from earlier +that modifies the position using the time? + +For this, we need to introduce joining. Joining starts with us specifying +multiple Component types and bitwise conditions. Don't be afraid, this is +simple. Here is an example: +join!(&positions_components && &size_components) + +This will create an iterator going through all entities that have both a +Position component AND a Size component. If you use &mut instead of &, then you +will get a mutable reference to the component in question. + +The join macro supports the following operators: && || ! +Those work as you would expect, with the caveat that operators are strictly read +from left to right. +For example, +join!(&a && &mut b || !&c) +creates an iterator where we only components of entities having the +following are included: they have (an A AND a B) OR do not have a C. +The reference to B will be mutable. + +Finally, when joining, what you get is actually: +(&Option<A>, &mut Option<B>, &Option<C>) + +The options are always present when joining over multiple components. + +Together: +``` +fn position_update_if_time(time: &Time, sizes: &Components<Size>, + positions: &mut Components<Position>) -> SystemResult { + if time.current_time >= 5 { + // Iterate over entities having both position and size, but updates + // only the position component. + for (pos, _) in join!(&mut positions && &size) { + pos.as_mut().unwrap().x -= 3; + } + } + Ok(()) +} +``` + +Comparison With Other ECS +-------------------------------------------------------------------------------- +Let's have a quick and informal comparison with other Rust ECS libraries. + +First, performance: According to the last time we ran benchmarks, we were the +fastest library when iterating over a single component. +For other benchmarks, including multiple component joining, entity creation and +deletion and component insertion, we ranked on average second, behind legion, +but sometimes being faster on some benchmarks. + +https://github.com/jojolepro/ecs_bench_suite/tree/planck + +Code Size: The complete code size of Planck ECS, including tests and benchmarks, +is under 1500 lines. +For comparison, Bevy ECS has 5400 lines of code, Specs has 6800, legion has +13000 and shipyard has 25000. + +SystemResult: As far as we know, we are the only ECS where systems return +errors gracefully in this way. + +Macros: System declaration, in most current ECS, either require a +macro-by-example or a procedural macro to be concise. Here, you declare +systems in a way identical to regular functions. + +Tests: We have high standards for tests. Since our code size is small, +all features and all non-trivial public functions are tested. +We also benchmarked all performance-sensitive code. + +Safety: We use unsafe code only as an absolute last resort. +This shows in the numbers. Here's the count of unsafe code snippets found in +popular ECS libraries: +- Specs: 150 +- Bevy ECS: 157 +- Legion: 264 +- Shipyard: 312 +- Planck ECS: 4 + +The numbers speak for themselves. + +Licensing +-------------------------------------------------------------------------------- +Published under CC0, Planck ECS is under the public domain and available for +free! + +Conclusion +-------------------------------------------------------------------------------- +In conclusion, Planck ECS is not an innovative piece of software. It does +the same thing that the community has been doing for years. It just does it in +a better and more safe way. + +If you like this library, please consider donating on patreon: +https://patreon.com/jojolepro + +https://github.com/jojolepro/planck_ecs +https://docs.rs/planck_ecs diff --git a/target/html/blog/2021-01-13_planks_ecs/index.html b/target/html/blog/2021-01-13_planks_ecs/index.html @@ -1,347 +0,0 @@ -<!doctype html> -<html lang=en> - <head> - <link href='data:image/png;base64,R0lGODlhGAAYAOeIAAAAALVSwLlUxLxXyLlZxMlb1cpc1hScoRSepNBf3c9h29Rg4NJh39Nh4BWhps9j3NJi3s1k2dVh4tBk3NVi4tNj39Zi49di49di5NBl3NVj4tNk39hi5dZj49Zj5Nli5dRk4ddj5NVk4thj5dti5tNl4Nhj5tZk49Fm3tlj5dRl4Nlj5tdk49dk5Npj5ddk5dVl4dhk5dZl49lk5tdl5dpk5tZm49Nn5NVn4tVn5dZn49Ro4Nlm5tNo5ddn5NBq29Vo4dVo4haortln5dRp4Ndo49Zp49Nq5Raqr9pp59hr5NFu5Nls5s1y5BevtBawtcp25BizuRi1uhm4vi20wbqG4hm7wRq8whm9w7yI4raM4Bq/xRq/xrSO4RrByBrDyRvDyhvFyxvGzBvHzRvHzqeb4BvIzhvIzxzIzxvJz6ad4CTH0aKf3xzK0BzK0RzL0p6i3xzN1BzP1R3P1pGr3h3Q15Or3RzR2CPP2R3R2B3R2R/R2RzS2R3S2SfP2Yex3Yey3TPN2TbN2Xm43DDP2Xa63EXK2kbK2v///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////yH+B2hhaSBoYWkAIfkEAQoA/wAsAAAAABgAGAAACP4A/wkcSLCgwYMFASgEgLDhQiFWrkxZ2JAgACl58vTZuDGPwor/ALzhyFEMmI1uGCIEUKfPHABkODoAMGZjHZUJ02wEEAUARwBofNrEOVBoH5EAzuyMA0Djxi9EAXD5mWdOS5JYo2Ld+pNjGpUAEGwVmrLPlqMkwSLhCgAqAgAHjG40wxCAE656ADzBQwWAHz5ZQ2LhashOlR4uSOToAkjQz5BesB5ic4QHiyQyiFiokQJKoZ8ATnIctCQGBCYqRHjYoWPIihUvyhA6CiDMxj10aFhI8MBGhghBZnTg8fp1FkIKa/aBk2LFhQUlbOAoooHDiRbFX2tR2EbPnw+vR0NMMAJEAQwQFBpoCDFCgoCFAOQEapJ9Q5EKDFCUKPKDAPyid6hhQnZK+GDAAPARVdQaN7yGQQEB/AdSSAlOaOGFAgUEADs=' - rel=icon> - <title>Jojolepro.com</title> - <meta charset=utf-8> - <meta name=description content="A bunch of stuff and things!"> - <style> - body{ - text-align:center; - overflow-y:scroll; - /*font:calc(0.75em + 1vmin) monospace;*/ - font: 1.3em monospace; - background-color: #181a1b; - border-color: #575757; - color: #e8e6e3; - } - pre{ - text-align:left; - display:inline-block; - } - img{ - max-width:80ch; - display:block; - height:auto; - width:100%; - } - nav a { - margin-right: 1ch; - } - a { - color: #3391ff; - } - * { - scrollbar-color: #2a2c2e #1c1e1f; - } - </style> - </head> - <body> - <nav> - <a href=/><b>Jojolepro</b></a> - <br/> - <a href=/blog>Blog</a> - <a href=/quotes>Quotes</a> - <a href=/projects>Projects</a> - <!--<a href=/focks>Focks</a>--> - <a href=https://git.jojolepro.com>Git</a> - <a href=https://github.com/jojolepro/>GitHub</a> - </nav> - <br/> - <article> - <pre> -Plank ECS: A Minimalistic Yet Performant Entity-Component-System Library -================================================================================ - -+------------------------------------------------------------------------------+ -| "Perfection is achieved, not when there is nothing more to add, | -| but when there is nothing left to take away." | -| - Antoine de Saint-Exupery, 1900 | -+------------------------------------------------------------------------------+ - -If you already know what an ECS is, jump to the -"Comparison With Other ECS" section. - -The title is a lie. In reality, this is an Entity-Component-Resource-System -library. - -First, let's start at the beginning. -What is an ECS you ask? -An ECS is a way to organise data and modify this data. - -Why not just use regular object oriented code? -For three reasons: -1) Using an ECS is often faster. -2) It uses parallelism to complete the data modifications much faster. -3) It looks much cleaner. - -Good! - -Now, let's cover the basics. - -The Basics --------------------------------------------------------------------------------- -We have four main elements. - -- Entity: A "thing" that exists in the world. It may be a game character, -a map, a button, anything! By itself, an Entity is a thing with no attributes -at all. Literally, it is just a thing that exists and is nothing. - -- Component: An attribute added to an Entity. This is what defines what the -"thing" really is. - -- Resource: Some data that is not attached to an Entity but also exists. -For example, time is a resource of our world, but isn't specific to any -Entity existing in the world (if we pretend that general relativity isn't a -thing, that is..) - -- System: An operation that transforms Entities, Components and Resources. - -Making It All Come Together --------------------------------------------------------------------------------- -Here's a quick example of how it looks conceptually: - -Entity 1: -- Name("Button") -- OnClick(Event::ButtonClicked) -- HoverAnimation("assets/button/on_hover.png") -- Position(5, 8) -- Size(10, 2) -- Render(Square, White) - -As you see, the entity is a thing where we "attach" components that -specify what it is. -We read this as: "Entity 1 is a thing with a name 'Button', that creates -an event when clicked, that is animated when hovered, has a physical -position and size and is rendered as a white square." - -Resources: -- Time(current_time) - -System: -- if current_time &gt; 5 seconds, then move all entities' Position left by 3 units. - -We have a simple system that conditionally modifies the Position component -of all entities having one. - -Extra Elements --------------------------------------------------------------------------------- -To make this all work together, we need some more concepts. - -First, the World. -A World is extremely simplistic: It holds all the entities, components and -resources. -Actually, that's how we used to do it. See, Plank ECS follows the minimalist -mindset. Our World stores only Resources, and everything else has been made a -Resource. Let's see how that works. - -For Entity, we store them in an Entities Resource. Simply a list of existing -entities, with some extra operations to create and kill entities. - -For Component, we store them in a Components&lt;T&gt; Resource. Similar to Entities, -it is a list. The main difference is that you access components using an Entity. - -A good way to think of it, even though it is not implemented this way, -is as the following: -Entities: List(Entity) -Components&lt;T&gt;: HashMap(Entity, T) - -Now, we have a way to contain entities, components and resources: the world. -What are we forgetting? Ah yes, the systems! -Where are they stored? -How do we execute them? -How do they get access to the data in World? - -Systems are stored in a Dispatcher. Dispatchers are built from a list of Systems -and are used to execute Systems either in sequence or in parallel. -The Dispatcher will fetch resources from the World automatically and execute -the System in a way that guarantees there will not be any conflicts while -accessing resources. - -To do this, Systems need to be built in a way that corresponds to what the -Dispatcher can handle. - -Constraints On Systems --------------------------------------------------------------------------------- -These are the constraints that specify how systems may be built: - -1) Systems must take only references as arguments. - -2) All mutable references must be after all immutable references. -For example: fn my_system(first: &amp;u32, second: &amp;u64, third: &amp;mut u16) -This constraint is attributable to the way traits are implemented for generic -types in rust. Removing this constraint would make the build time factorial, -which would effectively never complete. - -3) Systems must return a SystemResult. This is to gracefully handle and -recover from errors in systems. - -4) System arguments must implement Default. If they don't, then you need to use -&amp;Option&lt;WhatYouWant&gt; instead of directly using &amp;WhatYouWant. -This constraint exists so that resources may be automatically created for you, -as well as enforcing that any resource that might not exist is actually handled -by the system without any issue. - -How It Actually Looks --------------------------------------------------------------------------------- -Importing the library: -``` -use plank_ecs::*; -``` - -Creating an entity: -``` -let mut entities = Entities::default(); -let entity1 = entities.create(); -let entity2 = entities.create(); -``` - -Creating components: -``` -struct A; -let mut components = Components::default(); -components.insert(entity1, A); -``` - -Creating a world: -``` -let mut world = World::default(); -``` - -Creating a system: -``` -fn my_system(value: &amp;Components&lt;A&gt;) -&gt; SystemResult { - Ok(()) -} -``` - -Creating a system as a closure: -``` -let my_system = |value: &amp;Components&lt;A&gt;| Ok(()); -``` - -Creating and using a dispatcher: -``` -let dispatcher = DispatcherBuilder::default() - .add(my_system) - .build(&amp;mut world); -// Run without parallelism. -dispatcher.run_seq(&amp;mut world).expect("Error in a system!"); -// Run in parallel. -dispatcher.run_par(&amp;mut world).expect("Error in a system!"); - -// Does some cleanup related to deleted entities. -world.maintain(); -``` - -Joining Components --------------------------------------------------------------------------------- -The last part of the puzzle: How to write the example system from earlier -that modifies the position using the time? - -For this, we need to introduce joining. Joining starts with us specifying -multiple Component types and bitwise conditions. Don't be afraid, this is -simple. Here is an example: -join!(&amp;positions_components &amp;&amp; &amp;size_components) - -This will create an iterator going through all entities that have both a -Position component AND a Size component. If you use &amp;mut instead of &amp;, then you -will get a mutable reference to the component in question. - -The join macro supports the following operators: &amp;&amp; || ! -Those work as you would expect, with the caveat that operators are strictly read -from left to right. -For example, -join!(&amp;a &amp;&amp; &amp;mut b || !&amp;c) -creates an iterator where we only components of entities having the -following are included: they have (an A AND a B) OR do not have a C. -The reference to B will be mutable. - -Finally, when joining, what you get is actually: -(&amp;Option&lt;A&gt;, &amp;mut Option&lt;B&gt;, &amp;Option&lt;C&gt;) - -The options are always present when joining over multiple components. - -Together: -``` -fn position_update_if_time(time: &amp;Time, sizes: &amp;Components&lt;Size&gt;, - positions: &amp;mut Components&lt;Position&gt;) -&gt; SystemResult { - if time.current_time &gt;= 5 { - // Iterate over entities having both position and size, but updates - // only the position component. - for (pos, _) in join!(&amp;mut positions &amp;&amp; &amp;size) { - pos.as_mut().unwrap().x -= 3; - } - } - Ok(()) -} -``` - -Comparison With Other ECS --------------------------------------------------------------------------------- -Let's have a quick and informal comparison with other Rust ECS libraries. - -First, performance: According to the last time we ran benchmarks, we were the -fastest library when iterating over a single component. -For other benchmarks, including multiple component joining, entity creation and -deletion and component insertion, we ranked on average second, behind legion, -but sometimes being faster on some benchmarks. - -Code Size: The complete code size of Plank ECS, including tests and benchmarks, -is under 1500 lines. -For comparison, Bevy ECS has 5400 lines of code, Specs has 6800, legion has 13000, shipyard has -25000. - -SystemResult: As far as we know, we are the only ECS where systems return -errors gracefully in this way. - -Macros: System declaration, in most current ECS, either require a -macro-by-example or a procedural macro to be concise. Here, you declare -systems in a way identical to regular functions. - -Tests: We have high standards for tests. Since our code size is small, -all features and all non-trivial public functions are tested. -We also benchmarked all performance-sensitive code. - -Safety: We use unsafe code only as an absolute last resort. -This shows in the numbers. Here's the count of unsafe code snippets found in -popular ECS libraries: -- Specs: 150 -- Bevy ECS: 157 -- Legion: 264 -- Shipyard: 312 -- Plank ECS: 4 - -The numbers speak for themselves. - -Licensing --------------------------------------------------------------------------------- -Because of the quality and the time spent on this library, it was decided -that the AGPL license would be more adequate for it. We want the code to remain -open source and encourage contributions to come back. - -As we know some people want to make use of our product in commercial software, -we offer a paid commercial license as an alternative way to contribute back. - -Conclusion --------------------------------------------------------------------------------- -In conclusion, Plank ECS is not an innovative piece of software. It does -the same thing that the community has been doing for years. It just does it in -a better and more safe way. - -<a href='https://git.jojolepro.com/plank_ecs'>https://git.jojolepro.com/plank_ecs</a> -<a href='https://docs.rs/plank_ecs'>https://docs.rs/plank_ecs</a> -Part of the Focks Team project. -<a href='https://jojolepro.com/focks/'>https://jojolepro.com/focks/</a> - </pre> - </article> - <br/> - <footer> - <div> - (C) Joël Lupien 2020-2021 - </div> - <a href="/blog/2021-01-13_planks_ecs/index.txt">View page source</a> - </footer> - </body> -</html> diff --git a/target/html/blog/2021-01-13_planks_ecs/index.txt b/target/html/blog/2021-01-13_planks_ecs/index.txt @@ -1,285 +0,0 @@ -Plank ECS: A Minimalistic Yet Performant Entity-Component-System Library -================================================================================ - -#``` -+------------------------------------------------------------------------------+ -| "Perfection is achieved, not when there is nothing more to add, | -| but when there is nothing left to take away." | -| - Antoine de Saint-Exupery, 1900 | -+------------------------------------------------------------------------------+ -#``` - -If you already know what an ECS is, jump to the -"Comparison With Other ECS" section. - -The title is a lie. In reality, this is an Entity-Component-Resource-System -library. - -First, let's start at the beginning. -What is an ECS you ask? -An ECS is a way to organise data and modify this data. - -Why not just use regular object oriented code? -For three reasons: -1) Using an ECS is often faster. -2) It uses parallelism to complete the data modifications much faster. -3) It looks much cleaner. - -Good! - -Now, let's cover the basics. - -The Basics --------------------------------------------------------------------------------- -We have four main elements. - -- Entity: A "thing" that exists in the world. It may be a game character, -a map, a button, anything! By itself, an Entity is a thing with no attributes -at all. Literally, it is just a thing that exists and is nothing. - -- Component: An attribute added to an Entity. This is what defines what the -"thing" really is. - -- Resource: Some data that is not attached to an Entity but also exists. -For example, time is a resource of our world, but isn't specific to any -Entity existing in the world (if we pretend that general relativity isn't a -thing, that is..) - -- System: An operation that transforms Entities, Components and Resources. - -Making It All Come Together --------------------------------------------------------------------------------- -Here's a quick example of how it looks conceptually: - -Entity 1: -- Name("Button") -- OnClick(Event::ButtonClicked) -- HoverAnimation("assets/button/on_hover.png") -- Position(5, 8) -- Size(10, 2) -- Render(Square, White) - -As you see, the entity is a thing where we "attach" components that -specify what it is. -We read this as: "Entity 1 is a thing with a name 'Button', that creates -an event when clicked, that is animated when hovered, has a physical -position and size and is rendered as a white square." - -Resources: -- Time(current_time) - -System: -- if current_time > 5 seconds, then move all entities' Position left by 3 units. - -We have a simple system that conditionally modifies the Position component -of all entities having one. - -Extra Elements --------------------------------------------------------------------------------- -To make this all work together, we need some more concepts. - -First, the World. -A World is extremely simplistic: It holds all the entities, components and -resources. -Actually, that's how we used to do it. See, Plank ECS follows the minimalist -mindset. Our World stores only Resources, and everything else has been made a -Resource. Let's see how that works. - -For Entity, we store them in an Entities Resource. Simply a list of existing -entities, with some extra operations to create and kill entities. - -For Component, we store them in a Components<T> Resource. Similar to Entities, -it is a list. The main difference is that you access components using an Entity. - -A good way to think of it, even though it is not implemented this way, -is as the following: -Entities: List(Entity) -Components<T>: HashMap(Entity, T) - -Now, we have a way to contain entities, components and resources: the world. -What are we forgetting? Ah yes, the systems! -Where are they stored? -How do we execute them? -How do they get access to the data in World? - -Systems are stored in a Dispatcher. Dispatchers are built from a list of Systems -and are used to execute Systems either in sequence or in parallel. -The Dispatcher will fetch resources from the World automatically and execute -the System in a way that guarantees there will not be any conflicts while -accessing resources. - -To do this, Systems need to be built in a way that corresponds to what the -Dispatcher can handle. - -Constraints On Systems --------------------------------------------------------------------------------- -These are the constraints that specify how systems may be built: - -1) Systems must take only references as arguments. - -2) All mutable references must be after all immutable references. -For example: fn my_system(first: &u32, second: &u64, third: &mut u16) -This constraint is attributable to the way traits are implemented for generic -types in rust. Removing this constraint would make the build time factorial, -which would effectively never complete. - -3) Systems must return a SystemResult. This is to gracefully handle and -recover from errors in systems. - -4) System arguments must implement Default. If they don't, then you need to use -&Option<WhatYouWant> instead of directly using &WhatYouWant. -This constraint exists so that resources may be automatically created for you, -as well as enforcing that any resource that might not exist is actually handled -by the system without any issue. - -How It Actually Looks --------------------------------------------------------------------------------- -Importing the library: -``` -use plank_ecs::*; -``` - -Creating an entity: -``` -let mut entities = Entities::default(); -let entity1 = entities.create(); -let entity2 = entities.create(); -``` - -Creating components: -``` -struct A; -let mut components = Components::default(); -components.insert(entity1, A); -``` - -Creating a world: -``` -let mut world = World::default(); -``` - -Creating a system: -``` -fn my_system(value: &Components<A>) -> SystemResult { - Ok(()) -} -``` - -Creating a system as a closure: -``` -let my_system = |value: &Components<A>| Ok(()); -``` - -Creating and using a dispatcher: -``` -let dispatcher = DispatcherBuilder::default() - .add(my_system) - .build(&mut world); -// Run without parallelism. -dispatcher.run_seq(&mut world).expect("Error in a system!"); -// Run in parallel. -dispatcher.run_par(&mut world).expect("Error in a system!"); - -// Does some cleanup related to deleted entities. -world.maintain(); -``` - -Joining Components --------------------------------------------------------------------------------- -The last part of the puzzle: How to write the example system from earlier -that modifies the position using the time? - -For this, we need to introduce joining. Joining starts with us specifying -multiple Component types and bitwise conditions. Don't be afraid, this is -simple. Here is an example: -join!(&positions_components && &size_components) - -This will create an iterator going through all entities that have both a -Position component AND a Size component. If you use &mut instead of &, then you -will get a mutable reference to the component in question. - -The join macro supports the following operators: && || ! -Those work as you would expect, with the caveat that operators are strictly read -from left to right. -For example, -join!(&a && &mut b || !&c) -creates an iterator where we only components of entities having the -following are included: they have (an A AND a B) OR do not have a C. -The reference to B will be mutable. - -Finally, when joining, what you get is actually: -(&Option<A>, &mut Option<B>, &Option<C>) - -The options are always present when joining over multiple components. - -Together: -``` -fn position_update_if_time(time: &Time, sizes: &Components<Size>, - positions: &mut Components<Position>) -> SystemResult { - if time.current_time >= 5 { - // Iterate over entities having both position and size, but updates - // only the position component. - for (pos, _) in join!(&mut positions && &size) { - pos.as_mut().unwrap().x -= 3; - } - } - Ok(()) -} -``` - -Comparison With Other ECS --------------------------------------------------------------------------------- -Let's have a quick and informal comparison with other Rust ECS libraries. - -First, performance: According to the last time we ran benchmarks, we were the -fastest library when iterating over a single component. -For other benchmarks, including multiple component joining, entity creation and -deletion and component insertion, we ranked on average second, behind legion, -but sometimes being faster on some benchmarks. - -Code Size: The complete code size of Plank ECS, including tests and benchmarks, -is under 1500 lines. -For comparison, Bevy ECS has 5400 lines of code, Specs has 6800, legion has 13000, shipyard has -25000. - -SystemResult: As far as we know, we are the only ECS where systems return -errors gracefully in this way. - -Macros: System declaration, in most current ECS, either require a -macro-by-example or a procedural macro to be concise. Here, you declare -systems in a way identical to regular functions. - -Tests: We have high standards for tests. Since our code size is small, -all features and all non-trivial public functions are tested. -We also benchmarked all performance-sensitive code. - -Safety: We use unsafe code only as an absolute last resort. -This shows in the numbers. Here's the count of unsafe code snippets found in -popular ECS libraries: -- Specs: 150 -- Bevy ECS: 157 -- Legion: 264 -- Shipyard: 312 -- Plank ECS: 4 - -The numbers speak for themselves. - -Licensing --------------------------------------------------------------------------------- -Because of the quality and the time spent on this library, it was decided -that the AGPL license would be more adequate for it. We want the code to remain -open source and encourage contributions to come back. - -As we know some people want to make use of our product in commercial software, -we offer a paid commercial license as an alternative way to contribute back. - -Conclusion --------------------------------------------------------------------------------- -In conclusion, Plank ECS is not an innovative piece of software. It does -the same thing that the community has been doing for years. It just does it in -a better and more safe way. - -https://git.jojolepro.com/plank_ecs -https://docs.rs/plank_ecs -Part of the Focks Team project. -https://jojolepro.com/focks/ diff --git a/target/html/blog/blog.xml b/target/html/blog/blog.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> +<channel> +<title>Jojolepro Blog</title> +<description>The Blog of the Fockses!</description> +<link>https://www.jojolepro.com</link> +<atom:link href="https://www.jojolepro.com/blog/blog.xml" rel="self" type="application/rss+xml"/> +<item> +<title>Removing the US</title> +<link>https://www.jojolepro.com/blog/2021-01-13_removing_the_us</link> +</item> +<item> +<title>Planck: A Minimalistic Yet Performant ECS Library</title> +<link>https://www.jojolepro.com/blog/2021-01-13_planck_ecs</link> +</item> +<item> +<title>The Fall of the Giant</title> +<link>https://www.jojolepro.com/blog/2020-11-17_the_fall_of_the_giant</link> +</item> +<item> +<title>Event Chaining as a Decoupling Method in ECS Game Engines</title> +<link>https://www.jojolepro.com/blog/2020-08-20_event_chaining</link> +</item> +<item> +<title>Minimalist Alternatives to Popular Applications</title> +<link>https://www.jojolepro.com/blog/2020-07-07_minimalist_alternatives</link> +</item> +<item> +<title>Why I Moved Away From Wordpress</title> +<link>https://www.jojolepro.com/blog/2020-05-04_why_i_moved_from_wordpress</link> +</item> +<item> +<title>Simple School Documents</title> +<link>https://www.jojolepro.com/blog/2020-04-09_simple_school_documents</link> +</item> +<item> +<title>Extracting Data From Websites</title> +<link>https://www.jojolepro.com/blog/2020-03-31_extracting_data_from_websites</link> +</item> +</channel> +</rss> diff --git a/target/html/blog/index.gmi b/target/html/blog/index.gmi @@ -1,5 +1,5 @@ => 2021-01-13_removing_the_us Removing the US -=> 2021-01-13_planks_ecs Plank: A Minimalistic Yet Performant ECS Library +=> 2021-01-13_planck_ecs Planck: A Minimalistic Yet Performant ECS Library => 2020-11-17_the_fall_of_the_giant The Fall of the Giant => 2020-08-20_event_chaining Event Chaining as a Decoupling Method in ECS Game Engines => 2020-07-07_minimalist_alternatives Minimalist Alternatives to Popular Applications diff --git a/target/html/blog/index.html b/target/html/blog/index.html @@ -52,7 +52,7 @@ <article> <pre> <a href="2021-01-13_removing_the_us">Removing the US</a> -<a href="2021-01-13_planks_ecs">Plank: A Minimalistic Yet Performant ECS Library</a> +<a href="2021-01-13_planck_ecs">Planck: A Minimalistic Yet Performant ECS Library</a> <a href="2020-11-17_the_fall_of_the_giant">The Fall of the Giant</a> <a href="2020-08-20_event_chaining">Event Chaining as a Decoupling Method in ECS Game Engines</a> <a href="2020-07-07_minimalist_alternatives">Minimalist Alternatives to Popular Applications</a>