{"id":"hardlyardi/b226","name":"b226","scope":"hardlyardi","platform":"roblox","description":"Mirrored from the Wally registry.","version":"0.2.3","latest":"0.2.3","versions":["0.1.0","0.2.0","0.2.1","0.2.2","0.2.3"],"license":"MIT","licenseRating":"safe","licenseCaveats":[],"licenseVerified":true,"dependencies":{},"integrity":"51fa7e39523629c242f64ee89ffea1fe4a297ac9dc3700cc8cf9837a04eeee88","likes":0,"downloads":0,"install":"forest install hardlyardi/b226","url":"https://forest.dev/p/roblox/hardlyardi/b226","files":"https://api.forest.dev/ai/package/roblox/hardlyardi/b226/files","readme":"# B2?\r\n\r\nb2264644-3d77-4ab9-8a00-5e9ffb0ff964 is a robust, fast, and lightweight entity component system for Luau. It is fully\r\nunit-tested, and supports modern features on-par with similar libraries. B2 is still early in development (pre-1.0), so,\r\nexpect breaking changes on updates to 0.**X**.0 versions.\r\n\r\n## Why not B2?\r\n\r\nB2 is still very early in development. Naturally, this comes with bugs, breaking changes, and an immature ecosystem.\r\nI try my best to mitigate the former two with thorough unit tests, and a clear versioning scheme. However, if you choose\r\nto start using B2, beware the challenges that face early adopters of new software. Additionally, B2 includes (some)\r\nguarantees regarding querying safety which may hurt performance (benchmarks yet-to-be-seen).\r\n\r\n## Installation\r\n\r\nYou can install b226 via [Wally](https://wally.run/package/hardlyardi/b226?version=0.2.3)\r\n\r\n## Special Thanks\r\n\r\nSpecial thanks to [Marcus](https://github.com/ukendio/), [Sona](https://github.com/SolarScuffle-Bot), and others for\r\nmaking B2 possible. If you haven't seen [Jecs](https://github.com/ukendio/jecs) or you like B2, I think you should\r\ncheck it out. I learnt a lot about ECS from Jecs, and it's an awesome project.\r\n\r\n# Concepts\r\n\r\n## ECS\r\n\r\nYou need to create an ECS object to start using B2. It acts as storage for entities and their components, allows you to\r\nquery for state, and more. There is no limit to how many ECS objects you can create.\r\n\r\n```luau\r\nlocal b2 = require(path.to.b2)\r\n-- first argument is whether or not you're using the new solver\r\nlocal ecs = b2.ecs(false)\r\n```\r\n\r\n## Entities\r\n\r\nEntities represent containers for data in a game. Your game might have entities which look like characters, map objects,\r\nprojectiles, particles, etc. To give life to this entity, you'll need to add Components. For now, I'll use the built-in\r\ncomponent `b2.Name` as an example.\r\n\r\n```luau\r\n-- creates a new entity with no components and returns its identifier\r\nlocal alice = ecs.entity()\r\n\r\necs.set(entity, b2.Name, \"Alice\")\r\necs.get(entity, b2.Name) --> \"Alice\"\r\n```\r\n\r\nBy itself, an entity is just a unique number, and has no data. Using `ecs.contains`, you can check if an identifier\r\nexists as a valid entity.\r\n\r\n```luau\r\necs.contains(alice) --> true\r\n```\r\n\r\n## Base Operations\r\n\r\nIn the ECS, there are five base operations which can act on a single entity. These operations are `add`, `set`, `get`,\r\n`remove`, and `clear`.\r\n\r\n### `ecs.add(entity, Component)`\r\n\r\nAdds a component to an entity. If the entity already has the component, this will do nothing.\r\n\r\n### `ecs.set(entity, Component, Value)`\r\n\r\nSets the value of a component for an entity. If the component does not exist on the entity, it will be added.\r\n\r\n### `ecs.get(entity, Component, Components...): Values...`\r\n\r\nReturns the value of each component passed from an entity.\r\n\r\n### `ecs.remove(entity, Component)`\r\n\r\nRemoves a component from an entity. If the entity did not have the component, this will do nothing.\r\n\r\n### `ecs.clear(entity, delete?)`\r\n\r\nRemoves all references to this entity in the ECS storage. If `delete` is specified, the entity will have all of its\r\ncomponents removed, and be removed from the world. Entities which are not deleted will take up memory.\r\n\r\n## Bulk Operations\r\n\r\nBulk operations are like the base entity operations mentioned before, except they may operate on multiple components at\r\na time. There is some overhead from executing a bulk operation, but it should generally be very fast. You can expect\r\nbulk operations to mostly behave the same as regular entity operations being repeated.\r\n\r\n### `ecs.bulk_add(entity, { Components })`\r\n\r\nAdds a list of components to an entity.\r\n\r\n### `ecs.bulk_remove(entity, { Components })`\r\n\r\nRemoves a list of components from an entity.\r\n\r\n### `ecs.bulk_set(entity, { Components }, { Value })`\r\n\r\nSets the values for a list of components for an entity.\r\n\r\n### `ecs.bulk_get(entity, { Components }): { Values }`\r\n\r\nGets a list of values from a list of components from an entity.\r\n\r\n## Components are Entities\r\n\r\nIn the ECS, Components need unique Identifiers, just like entities. In B2, this problem is solved by making each\r\ncomponent a unique entity of its own. Because components are entities, you can apply components to other components. You\r\ncan use `ecs.has` with `b2.Component` to check if an Id is a component:\r\n\r\n```luau\r\nlocal entity = ecs.entity()\r\nlocal Component = ecs.component()\r\necs.has(entity, b2.Component) --> false\r\necs.has(Component, b2.Component) --> true\r\n```\r\n\r\nIf a game has a component, that means that component is an entity, and you can give it metadata. Components which are\r\napplied to components will be referred to as 'traits' from here on.\r\n\r\n```luau\r\nlocal Position = ecs.component() :: b2.Id<Vector3>\r\n-- Using regular APIs to set traits on components!\r\necs.set(Position, b2.Name, \"Position\")\r\n\r\nprint(`{ecs.get(Position, b2.Name)} is a Component: {ecs.has(Position, b2.Component)}`);\r\n```\r\n\r\n### Entities are Components (Tags)\r\n\r\nEntities can also be used as a kind of component called a 'Tag'. This is a component with no data, but which can be\r\nchecked with `ecs.has(e, Tag)`:\r\n\r\n```luau\r\nlocal IsAwesome = ecs.entity()\r\n\r\nlocal bob = ecs.entity()\r\necs.add(bob, IsAwesome)\r\necs.has(bob, IsAwesome) --> true\r\n```\r\n\r\n## Queries\r\n\r\nQueries are the main method for you to look for a group of entities and operate on them. Queries are used for a lot of\r\nthings, but a simple example is looking for a single component.\r\n\r\n```luau\r\nlocal Tag = ecs.entity()\r\nlocal alice = ecs.entity()\r\nlocal bob = ecs.entity()\r\necs.set(alice, Name, \"Alice\")\r\necs.add(alice, Tag)\r\necs.set(bob, Name, \"Bob\")\r\necs.add(bob, Tag)\r\n\r\nfor entity, name in ecs.query(Name):with(Tag):entities() do\r\n    print(entity, name)\r\nend\r\n-- Output:\r\n--  1026 Alice\r\n--  1027 Bob\r\n```\r\n\r\n`query:entities()` can be run multiple times and the query will remain accurate. E.g.,\r\n\r\n```luau\r\nlocal Tag = ecs.entity()\r\nlocal e1 = ecs.entity()\r\nlocal e2 = ecs.entity()\r\necs.add(e1, Tag)\r\nlocal q = ecs.query(Tag):without(Name)\r\nfor e in q:entities() do\r\n    print(e) -- outputs only e1\r\nend\r\necs.add(e2, Tag)\r\nfor e in q:entities() do\r\n    print(e) -- outputs e1 and e2\r\nend\r\n```\r\n\r\n## Relationships\r\n\r\nEntity relationships make it possible to describe a graph of entities efficiently in your data.\r\n\r\nAdding/removing relationships is similar to adding/removing regular components, with as difference that instead of a\r\nsingle component id, a relationship adds a pair of two things to an entity. In this pair, the first element represents\r\nthe relationship (e.g. \"Eats\"), and the second element represents the target (e.g. \"Apples\").\r\n\r\nRelationships can be used to describe many things, from hierarchies, to status effects, to even transactions between\r\nplayer inventories. They can be created with `b2.pair(relationship, target)`. To get the target of a relationship, use\r\n`ecs.target`:\r\n\r\n### `ecs.target(e, rel, idx?)`\r\n\r\nGets the target of a relationship. A relationship is nonexclusive, meaning it can have multiple targets. Because of\r\nthis, `target` has an optional index - which starts at and will default to zero. This could be used to iterate all\r\ntargets of a relationship for an entity.\r\n\r\nMore info can be found on the\r\n[Jecs Documentation](https://ukendio.github.io/jecs/learn/overview.html#relationships).\r\n\r\n## Exclusive Relationships\r\n\r\nYou can mark a tag or component as an exclusive relationship (i.e., it may only have one target at a time) with\r\n`b2.Exclusive`:\r\n\r\n```luau\r\nlocal ChildOf = ecs.component()\r\necs.add(ChildOf, b2.Exclusive)\r\n\r\nlocal e1 = ecs.entity()\r\nlocal e2 = ecs.entity()\r\nlocal e3 = ecs.entity()\r\n\r\necs.add(e3, pair(ChildOf, e1))\r\n-- Removes the previous pair from e3, and sets a new target\r\necs.add(e3, pair(ChildOf, e2))\r\n```\r\n\r\n## Wildcards\r\n\r\n`b2.Wildcard` pairs allow you to query for less 'Specific' relationships. `pair(relation, Wildcard)` and\r\n`pair(Wildcard, target)` are both valid for queries.\r\n\r\n```luau\r\nfor entity in ecs.query(pair(Likes, b2.Wildcard)):entities() do\r\n    print(`entity {entity} likes {ecs.target(entity, Likes)}`)\r\nend\r\n```\r\n\r\n## Cleanup Traits\r\n\r\nWhen entities that are used as tags, components, relationships or relationship targets are deleted, cleanup traits\r\nensure that the store does not contain any dangling references. Any cleanup policy provides this guarantee, so while\r\nthey are configurable, games cannot configure traits that allows for dangling references.\r\n\r\nTo configure a cleanup policy for an entity, a (Condition, Action) pair can be added to it. If no policy is specified,\r\nthe id will be removed on cleanup.\r\n\r\nRight now, there are six cleanup conditions:\r\n\r\n- `OnClear`: the component or tag is cleared or deleted.\r\n- `OnClearTarget`: a target used with the relationship is cleared or deleted.\r\n- `OnClearAsRelation`: this entity is the first part of a relationship pair, and is being cleared or deleted.\r\n- `OnDelete`: the component or tag is deleted.\r\n- `OnDeleteTarget`: a target used with the relationship is deleted.\r\n- `OnDeleteAsRelation`: this entity is the first part of a relationship pair, and is being deleted.\r\n\r\nAnd one cleanup action:\r\n\r\n- `CleanupDelete`: When the condition is met, entities with this ID will be deleted.\r\n\r\n### pair(OnClear, CleanupDelete)\r\n\r\n```luau\r\nlocal Archer = ecs.component()\r\necs.add(Archer, b2.pair(b2.OnClear, b2.CleanupDelete))\r\n\r\nlocal e = ecs.entity()\r\necs.add(e, Archer)\r\n\r\n-- This will delete e because Archer has an (OnClear, CleanupDelete) trait.\r\necs.clear(Archer, true)\r\n```\r\n\r\n### (CleanupOnClearTarget, CleanupDelete)\r\n\r\n```luau\r\nlocal ChildOf = ecs.component()\r\necs.add(ChildOf, pair(b2.CleanupOnClearTarget, b2.CleanupDelete))\r\necs.add(ChildOf, b2.Exclusive)\r\n\r\nlocal parent = ecs.entity()\r\nlocal child = ecs.entity()\r\necs:add(child, pair(ChildOf, parent))\r\n\r\n-- This will delete both parent and child\r\necs.clear(parent, true)\r\n```\r\n\r\n## Hooks\r\n\r\nSometimes you may want to ensure certain things are true of your components. For example, you may want to make sure that\r\nany entity which recieves an Interactable component also creates a ProximityPrompt and has a component to represent it.\r\nThis is where hooks come in.\r\n\r\nThere are three typs of hooks. `OnAdd`, `OnChange`, and `OnRemove`. You can set traits for a components' hooks via\r\n`ecs.set(Component, On*, callback)`. The callback is defined for each as:\r\n\r\n```luau\r\ntype OnAdd<Data> = (entity: Ent, id: Ent<Data>, data: Data) -> ()\r\ntype OnChange<Data> = (entity: Ent, id: Ent<Data>, data: Data) -> ()\r\ntype OnRemove<Data> = (entity: U53, id: Ent<Data>) -> ()\r\n```\r\n\r\nOnce a Component exists on any entity, hook callbacks for that Component cannot ever be safely added, changed, or\r\nremoved. To check if modifying a Component's hooks is 'safe', you may use `ecs.in_use(Component)`:\r\n\r\n```luau\r\nlocal Component = ecs.component()\r\nlocal entity = ecs.entity()\r\necs.in_use(Component) --> false\r\necs.add(entity, Component)\r\necs.in_use(Component) --> true\r\necs.remove(entity, Component)\r\necs.in_use(Component) --> true\r\n```\r\n\r\nBecause only one component hook kind can exist per Component, it's important that hooks be used correctly. Hooks are\r\nintended for enforcing invariants, I.e., ensuring that something is always true under certain conditions. Here's an\r\nexample of how you could use hooks:\r\n\r\n```luau\r\nlocal Player = ecs.component()\r\nlocal PlayerSaveData = ecs.component()\r\n\r\necs.set(Player, b2.OnAdd, function(entity)\r\n    ecs.set(entity, PlayerSaveData, ...)\r\nend)\r\n```\r\n\r\n## Monitors\r\n\r\nMonitors are similar to Hooks, but with a few key differences. Only one Hook of each kind can exist per Component, and\r\nhooks cannot be changed or removed. You can modify an entity from inside a hook. Monitors are the contrary - multiple of\r\nthem can exist, they can be added or removed at any time, and you cannot modify entities safely from inside monitors.\r\nThat all said, you can also expect monitors to be significantly slower than listening with hooks.\r\n\r\nAnother difference is that monitors allow you to broadly percieve structural changes based on queries. You can create a\r\nnew monitor with `query:monitor_added(callback)` or `query:monitor_removed(callback)`. Monitors will inform you when an\r\nentity starts, or stops matching a query.\r\n\r\n### `query:monitor_added<Reads...>(callback: (entity, Reads...) -> ())`\r\n\r\nCalls when an entity has started matching the query, as well as all of the data which a query normally reads.\r\n\r\n### `query:monitor_removed(callback: (entity) -> ())`\r\n\r\nCalls when an entity has stopped matching the query.\r\n\r\n## Each\r\n\r\nSometimes you may need to iterate through every entity which has a single component without reading its value. This can\r\nbe achieved with `ecs.each`:\r\n\r\n```luau\r\nlocal e1 = ecs.entity()\r\nlocal e2 = ecs.entity()\r\necs.add(e1, Name \"Alice\")\r\necs.set(e2, Name, \"Bob\")\r\n\r\nlocal has_name = {}\r\nfor entity in ecs.each(Name) do\r\n    has_name[entity] = true\r\nend\r\n```\r\n\r\n## Builtin Components\r\n\r\nThese components are properties of `b2`. User-facing builtin components include:\r\n\r\n- Component\r\n- Name\r\n- Wildcard\r\n- ChildOf\r\n- DeleteOnClear\r\n- DeleteOnDelete\r\n- DeleteOnClearTarget\r\n- DeleteOnDeleteTarget\r\n- DeleteOnClearAsRelation\r\n- DeleteOnDeleteAsRelation\r\n- OnClear\r\n- OnDelete\r\n- OnClearTarget\r\n- OnDeleteTarget\r\n- CleanupDelete\r\n- OnAdd\r\n- OnChange\r\n- OnRemove\r\n- Exclusive\r\n","readmeTruncated":false}