From 258ceed1990bfdb65c0a682c7cc765f41b6b3ebc Mon Sep 17 00:00:00 2001 From: Anselm Hook Date: Wed, 27 Dec 2023 16:30:15 -0500 Subject: [PATCH 01/10] note re segue --- docs/01_gettingStarted/03_developer/02_typescript.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/01_gettingStarted/03_developer/02_typescript.md b/docs/01_gettingStarted/03_developer/02_typescript.md index bb989a3be783..56cb4664821a 100644 --- a/docs/01_gettingStarted/03_developer/02_typescript.md +++ b/docs/01_gettingStarted/03_developer/02_typescript.md @@ -212,4 +212,8 @@ The general flow is like so: ### PongComponent, PongGameSystem and PongPhysicsSystem -[tbd] \ No newline at end of file +[tbd] + +### Segue + +[tbd] From d4622a3a4c3634415779c59183a5df18e5b622df Mon Sep 17 00:00:00 2001 From: Anselm Hook Date: Wed, 27 Dec 2023 17:22:47 -0500 Subject: [PATCH 02/10] removed bracket --- docs/01_gettingStarted/03_developer/02_typescript.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/01_gettingStarted/03_developer/02_typescript.md b/docs/01_gettingStarted/03_developer/02_typescript.md index 56cb4664821a..30ca7148c05e 100644 --- a/docs/01_gettingStarted/03_developer/02_typescript.md +++ b/docs/01_gettingStarted/03_developer/02_typescript.md @@ -94,14 +94,14 @@ In PaddleSystem.ts we see a good example of this reactive state pattern. The app ``` export const PaddleState = defineState({ name: 'ee.pong.PaddleState', - initial: {} as Record< + initial: {} as Record EntityUUID, { owner: UserID handedness: 'left' | 'right' gameEntityUUID: EntityUUID } - > + ... ``` From 2e8317ff87ddc50ead92b3f48c3b2de5479ceb40 Mon Sep 17 00:00:00 2001 From: Anselm Hook Date: Wed, 27 Dec 2023 18:34:52 -0500 Subject: [PATCH 03/10] escaping brackets? --- docs/01_gettingStarted/03_developer/02_typescript.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/01_gettingStarted/03_developer/02_typescript.md b/docs/01_gettingStarted/03_developer/02_typescript.md index 30ca7148c05e..50624f2390fb 100644 --- a/docs/01_gettingStarted/03_developer/02_typescript.md +++ b/docs/01_gettingStarted/03_developer/02_typescript.md @@ -94,14 +94,14 @@ In PaddleSystem.ts we see a good example of this reactive state pattern. The app ``` export const PaddleState = defineState({ name: 'ee.pong.PaddleState', - initial: {} as Record + initial: {} as Record\< EntityUUID, { owner: UserID handedness: 'left' | 'right' gameEntityUUID: EntityUUID } - + \> ... ``` From ee6a18b7beac11417d4cd9b11bcf6e72663bc9e9 Mon Sep 17 00:00:00 2001 From: Anselm Hook Date: Wed, 27 Dec 2023 19:13:07 -0500 Subject: [PATCH 04/10] docusaurus disallows <> without escaping except in code blocks, also { something } is treated as some kind of variable and should not be used otherwise --- .../01_gettingStarted/03_developer/00_intro.md | 2 +- .../03_developer/02_typescript.md | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/01_gettingStarted/03_developer/00_intro.md b/docs/01_gettingStarted/03_developer/00_intro.md index ba4c24ed519c..f4760a226de2 100644 --- a/docs/01_gettingStarted/03_developer/00_intro.md +++ b/docs/01_gettingStarted/03_developer/00_intro.md @@ -8,6 +8,6 @@ TODO: This page should contain: --> _This page will contain an introduction to the Getting Started: Developer guides._ -_Programming in Ethereal Engine can be done through [Typescript](/docs/manual/developer/intro)._ +_Programming in Ethereal Engine can be done through [Typescript](/docs/manual/developer/typescript/intro)._ _But the engine also has a NoCode alternative to programming, called [Behave Graph](/docs/manual/developer/behaveGraph/intro)._ diff --git a/docs/01_gettingStarted/03_developer/02_typescript.md b/docs/01_gettingStarted/03_developer/02_typescript.md index 50624f2390fb..2010f09f3242 100644 --- a/docs/01_gettingStarted/03_developer/02_typescript.md +++ b/docs/01_gettingStarted/03_developer/02_typescript.md @@ -8,12 +8,15 @@ NOTE: This page should contain: - Guide: Teaches a new user how to program the Hero Project and be comfortable with EE project development. - Segue: Lead the user into the Developer Manual --> +_This guide will teach you how to get started programming with Ethereal Engine using Typescript._ +_Visit the [Typescript: Introduction](/docs/manual/developer/typescript/intro) page for more details._ + ## Overview -'Pong' is a simple multiplayer game in Ethereal Engine built using Typescript. It is an example of best practices for developers. +We're going to look at 'Pong', a multiplayer game that we've built in Ethereal Engine using Typescript. It is an example of best practices for developers. -## Installation and Running +## Installation and Running Pong 1) Ethereal Engine scans for projects mounted in the /packages/projects/projects sub-folder of Ethereal Engine. From scratch we can install Ethereal Engine itself and also register a sub project using the following: @@ -94,18 +97,18 @@ In PaddleSystem.ts we see a good example of this reactive state pattern. The app ``` export const PaddleState = defineState({ name: 'ee.pong.PaddleState', - initial: {} as Record\< + initial: {} as Record< EntityUUID, { owner: UserID handedness: 'left' | 'right' gameEntityUUID: EntityUUID } - \> + > ... ``` -The defineState() method registers a collection of Record<> objects. A Record is a schema in a third party runtime schema definition language that Ethereal Engine uses. In this case the Record declares that this state object is a hashed collection of EntityUUID to { owner, handedness, gamEntityUUID } objects. +The defineState() method registers a collection of Record objects. A Record is a schema in a third party runtime schema definition language that Ethereal Engine uses. ### PaddleSystem: Introduction to Event Sourced State @@ -208,12 +211,9 @@ The general flow is like so: ## PlateComponent -[tbd] ### PongComponent, PongGameSystem and PongPhysicsSystem -[tbd] -### Segue +### Summary -[tbd] From a3fe2e259654ef0b11b958e34a56086dbb2df2a5 Mon Sep 17 00:00:00 2001 From: "Ivan Mar (sOkam!)" <7308253+heysokam@users.noreply.github.com> Date: Thu, 28 Dec 2023 12:59:36 -0800 Subject: [PATCH 05/10] doc: Improvement to the readme guidelines --- readme.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/readme.md b/readme.md index 7fb1d13abd68..639b17555b3a 100644 --- a/readme.md +++ b/readme.md @@ -23,6 +23,11 @@ - Underscore `_` for italics - End lines with double space ` ` for newline breaks within the same paragraph. - Always assign a valid coding language to code blocks _(necessary for syntax highlighting)_. +- Always use `N. Text` for numbered bullet points. (eg: 1. First point) +- Align newline subtext of bullet points to the text of the bullet point, so that the subtext becomes a part of the bullet point alignment rules. +- Align the first paragraph after a title right next to their title, without an empty newline in between the text and the title. + (preferable, not mandatory. easier to pattern/block skim-read internally, and doesn't affect the rendered html) + ## Docusaurus: Routing - Always name files/folder with the format: `NN_theFile.md` From 65c7e51be9836eae723500234e6e4bce62399fe6 Mon Sep 17 00:00:00 2001 From: "Ivan Mar (sOkam!)" <7308253+heysokam@users.noreply.github.com> Date: Thu, 28 Dec 2023 13:00:06 -0800 Subject: [PATCH 06/10] fmt: Temporary commit for anselm to preview --- .../03_developer/02_typescript.md | 128 ++++++++++-------- 1 file changed, 74 insertions(+), 54 deletions(-) diff --git a/docs/01_gettingStarted/03_developer/02_typescript.md b/docs/01_gettingStarted/03_developer/02_typescript.md index 2010f09f3242..f908dacbc637 100644 --- a/docs/01_gettingStarted/03_developer/02_typescript.md +++ b/docs/01_gettingStarted/03_developer/02_typescript.md @@ -8,57 +8,64 @@ NOTE: This page should contain: - Guide: Teaches a new user how to program the Hero Project and be comfortable with EE project development. - Segue: Lead the user into the Developer Manual --> -_This guide will teach you how to get started programming with Ethereal Engine using Typescript._ -_Visit the [Typescript: Introduction](/docs/manual/developer/typescript/intro) page for more details._ - -## Overview +This guide will teach you how to get started programming with Ethereal Engine. -We're going to look at 'Pong', a multiplayer game that we've built in Ethereal Engine using Typescript. It is an example of best practices for developers. +You will learn how to create **Pong**, a multiplayer game built with Ethereal Engine using Typescript. -## Installation and Running Pong +> _This is an introductory guide._ +> _Visit the [Developer Manual](/docs/manual/developer/intro) page for more information about programming with Ethereal Engine._ -1) Ethereal Engine scans for projects mounted in the /packages/projects/projects sub-folder of Ethereal Engine. From scratch we can install Ethereal Engine itself and also register a sub project using the following: - -``` -gh repo clone EtherealEngine/EtherealEngine -cd EtherealEngine +## Installation and Running the project +### 1. Install Pong + +Ethereal Engine scans for projects in its `/packages/projects/projects` sub-folder. +We can install Ethereal Engine from scratch and also register a sub project using the following commands: +```bash +git clone https://github.com/EtherealEngine/etherealengine +cd etherealengine cd packages/projects/projects -gh repo clone EtherealEngine/ee-tutorial-pong +git clone https://github.com/EtherealEngine/ee-tutorial-pong cd ../../../ ``` -2) A fresh install of Ethereal Engine can be run like so: - -``` +### 2. Run the engine +A fresh install of Ethereal Engine can be run with: +```bash npm install npm run dev ``` -3) Once Ethereal Engine itself is running, from the web admin panel of Ethereal Engine create a 'location' for the project. See https://localhost:3000/admin . Map the project to the name 'pong'. +### 3. Configure the Location +Go to the Admin Panel, create a `Location` for the project and map the project to the name `pong`. +:::info[link] +https://localhost:3000/admin . +::: + +:::important +Ethereal Engine must be running for this step and the rest of this guide. +::: 4) Run the project on the web by visiting it with the URL you created. See https://localhost:3000/location/pong ## Understanding Pong -### A 10000 foot overview - -Pong has several files which are roughly represent parts of the experience: +### Quick Overview +A Pong world can have several separate pong tables at once. +Each pong table has four pong plates and can handle four players at once. -- PaddleSystem -> a paddle 'system' for managing the player paddles specifically. -- PlateComponent -> each pong game has one or more plates that represent player positions in a game. In our case we have 4 player games. -- PongComponent -> each pong game in a larger pong world has a concept of a game starting or stopping; this is managed here. -- PongGameSystem -> a game 'system' for managing each game instance; starting and stopping play and dealing with players. -- PongPhysicsSystem -> a ball spawner and scoring 'system' for the pong game overall -- worldinjection -> bootstraps the game - -A 'Pong' world can have several separate pong tables at once. Each pong table has four pong plates and can handle four players at once. +Pong has several files which represent parts of the experience: +- **PaddleSystem**: A paddle 'system' for managing the player paddles. +- **PlateComponent**: Each pong game has one or more plates that represent player positions in a game. This example creates 4 player games. +- **PongComponent**: Each pong game in has the concept of a game starting or stopping; this is managed here. +- **PongGameSystem**: A game system for managing each game instance; starting and stopping play and dealing with players. +- **PongPhysicsSystem**: A ball spawner and scoring 'system' for the pong game overall +- **worldinjection**: Connects the project's code with the engine's code. ### World Injection - -Ethereal Engine projects typically have a worldinjection.ts file to bootstrap a project. In Pong this file registers and starts three 'systems', and does little else: - -``` +Ethereal Engine projects have a `worldinjection.ts` file to connect the project code to with the engine so it can be run _(aka bootstrapping / injection)_. +For Pong this file registers and starts three systems, and does little else: +```ts import './PaddleSystem' import './PongGameSystem' import './PongPhysicsSystem' @@ -66,35 +73,44 @@ export default async function worldInjection() {} ``` ### Entities, Components and Systems +Ethereal Engine uses an ECS (Entity Component System). +**Entities** in a game are entirely defined by their **Components** or 'capabilities'. +**Systems** drive the state of the entire application over time by running every frame over those components. -Ethereal Engine uses an ECS (Entity Component System). Entities in a game are entirely defined by their Components or 'capabilities', and systems driven the state of the entire application over time by running every frame over those components. One benefit of an ECS is that it allows a highly granular 'vocabulary' for designers to express game ideas with. - -The ECS we use currently BitECS. Another good ECS is Flecs: - -- https://github.com/NateTheGreatt/bitECS -- https://news.ycombinator.com/item?id=35434374 +A great benefit of the ECS pattern is that it allows a highly granular 'vocabulary' for designers to express game ideas with. -In Pong there are three systems imported via worldInjection above. Each is responsible for a different part of the experience: +:::info +Ethereal Engine currently uses [bitECS](https://github.com/NateTheGreatt/bitECS) for its ECS implementation. +Another great ECS library to look into is [Flecs](https://news.ycombinator.com/item?id=35434374). +::: -1) PaddleSystem -2) PongPhysicsSystem -3) PongGameSystem +Pong imports three systems via worldInjection. Each one is responsible for a different part of the Pong experience: +1. PaddleSystem +2. PongPhysicsSystem +3. PongGameSystem -We will look at the PaddleSystem.ts first. +We will look at `PaddleSystem.ts` first. ### PaddleSystem: Introduction to State and Reactivity +In Pong each active player has two paddles (one for the left hand and one for the right hand), and paddles are spawned or destroyed as players come and go. -Ethereal Engine uses the React pattern of allowing state observers to 'react' to state changes. This is done for a couple of different reasons. Philosophically it separates the 'what' from the 'how', and technically it helps decouple game components from each other, allowing developers to scale work horizontally, reducing dependencies. +We will use Ethereal Engine's best practices and separate our concerns. +What this means for us, at this level of abstraction, is that we don't yet need to think about how the paddles are manifested. -The React website has several good discussions on reactivity as a whole: +We will separate the 'what' from the 'how' of the paddles: +- **what**: There are some set of paddles. _(Entities)_ +- **how**: The paddles happen to be 3D objects with collision hulls. _(Components)_ + > We will get into the details of how this works later. -- https://react.dev/learn/reacting-to-input-with-state +:::info[advanced] +Ethereal Engine uses the React pattern of allowing state observers to 'react' to state changes. +You can read more in depth about this in the [ECS section](/docs/manual/developer/ecs) of the manual. +::: -In Pong each active player has two paddles (one for the left hand and one for the right hand), and paddles are spawned or destroyed as players come and go. In this case the 'what' is that there are some set of paddles. And the 'how' (which we will get to later) is that the paddles happen to be 3d objects with collision hulls. But at this level of scope we can separate our concerns and we don't have to think about how the paddles are manifested. -In PaddleSystem.ts we see a good example of this reactive state pattern. The approach we've taken here is to track the enumeration of active paddles like so: - -``` +In `PaddleSystem.ts` we can see a good example of the reactive state pattern that Ethereal Engine follows. +The basic idea here is to track the list of active paddles: +```ts export const PaddleState = defineState({ name: 'ee.pong.PaddleState', initial: {} as Record< @@ -108,15 +124,19 @@ export const PaddleState = defineState({ ... ``` -The defineState() method registers a collection of Record objects. A Record is a schema in a third party runtime schema definition language that Ethereal Engine uses. +:::info[advanced] +The `defineState()` method registers a collection of Record objects. +A `Record` is a schema in a third party runtime schema definition language that Ethereal Engine uses. +::: ### PaddleSystem: Introduction to Event Sourced State +Ethereal Engine uses an event sourced state paradigm. +Sourcing state and responding to that state is asynchronous but a single 'effect' or outcome results, rather than having to propagate potentially thousands of successive state changes. -Ethereal Engine uses an event sourced state paradigm. Sourcing state and responding to that state is asynchronous but a single 'effect' or outcome results, rather than having to propagate potentially thousands of successive state changes. - -A good discussion of Event Sourcing can be found here: - +:::info +A good discussion about Event Sourcing can be found here: https://domaincentric.net/blog/event-sourcing-snapshotting +::: In an Event Sourced system, the current state of an aggregate is usually reconstituted from the full history of events. It means that before handling a command we need to do a full read of a single fine-grained stream and transport the events over the network. This allows late joiners to synchronize with the overall game state. From 67c2ff8f9b017565c56b0f5521108b8553660ee7 Mon Sep 17 00:00:00 2001 From: "Ivan Mar (sOkam!)" <7308253+heysokam@users.noreply.github.com> Date: Thu, 28 Dec 2023 17:31:06 -0800 Subject: [PATCH 07/10] fmt: Small formatting changes to the article --- .../03_developer/02_typescript.md | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/docs/01_gettingStarted/03_developer/02_typescript.md b/docs/01_gettingStarted/03_developer/02_typescript.md index f908dacbc637..ebab552adbd7 100644 --- a/docs/01_gettingStarted/03_developer/02_typescript.md +++ b/docs/01_gettingStarted/03_developer/02_typescript.md @@ -140,9 +140,8 @@ https://domaincentric.net/blog/event-sourcing-snapshotting In an Event Sourced system, the current state of an aggregate is usually reconstituted from the full history of events. It means that before handling a command we need to do a full read of a single fine-grained stream and transport the events over the network. This allows late joiners to synchronize with the overall game state. -In PaddleSystem we define a set of actions explicitly like so: - -``` +In `PaddleSystem.ts` we define a set of actions explicitly like so: +```ts export class PaddleActions { static spawnPaddle = defineAction({ ...WorldNetworkAction.spawnObject.actionShape, @@ -157,7 +156,7 @@ export class PaddleActions { And we then allow the registration of 'receptors' on state objects to catch dispatched events over the network, and in this case we're entirely focused on updating the state records above: -``` +```ts ... receptors: [ [ @@ -188,15 +187,15 @@ With the state management out of the way, now we're left with the details of mak PaddleReactor defines a React component that has a useEffect() to observe state changes on a given PaddleState entry. When the PaddleState changes it sets up an entity to reflect that owner. Inside the useEffect() we see several typical 3d and game related components being setup: - - UUIDComponent - - TransformComponent - - VisibleComponent - - DistanceFromCameraComponent - - FrustrumCullComponent - - NameComponent - - PrimitiveGeometryComponent - - ColliderComponent - - GrabbableComponent +- UUIDComponent +- TransformComponent +- VisibleComponent +- DistanceFromCameraComponent +- FrustrumCullComponent +- NameComponent +- PrimitiveGeometryComponent +- ColliderComponent +- GrabbableComponent Most of these components are self descriptive, and this typically reflects the core set of components you'll see in many Ethereal Engine 3d entities that represent objects in a game. From 88ee0f01573a0b1d784d77c283d6f25541840686 Mon Sep 17 00:00:00 2001 From: "Ivan Mar (sOkam!)" <7308253+heysokam@users.noreply.github.com> Date: Fri, 29 Dec 2023 11:14:42 -0800 Subject: [PATCH 08/10] fmt: Completed review of the current state of the article --- .../03_developer/02_typescript.md | 92 +++++++++++-------- 1 file changed, 53 insertions(+), 39 deletions(-) diff --git a/docs/01_gettingStarted/03_developer/02_typescript.md b/docs/01_gettingStarted/03_developer/02_typescript.md index ebab552adbd7..bbbb3a5363b9 100644 --- a/docs/01_gettingStarted/03_developer/02_typescript.md +++ b/docs/01_gettingStarted/03_developer/02_typescript.md @@ -9,16 +9,18 @@ NOTE: This page should contain: - Segue: Lead the user into the Developer Manual --> -This guide will teach you how to get started programming with Ethereal Engine. +:::important +This guide teaches some of the Ethereal Engine recommended best practices for developers. +As such, the level of knowledge required will be higher than if it was just an introductory guide. -You will learn how to create **Pong**, a multiplayer game built with Ethereal Engine using Typescript. +_Visit the [Developer Manual](/docs/manual/developer/intro) page for in-depth information about programming with Ethereal Engine._ +::: -> _This is an introductory guide._ -> _Visit the [Developer Manual](/docs/manual/developer/intro) page for more information about programming with Ethereal Engine._ +This guide will teach you how to create **Pong**, a multiplayer game built with Ethereal Engine using Typescript. ## Installation and Running the project ### 1. Install Pong - + Ethereal Engine scans for projects in its `/packages/projects/projects` sub-folder. We can install Ethereal Engine from scratch and also register a sub project using the following commands: ```bash @@ -37,24 +39,28 @@ npm run dev ``` ### 3. Configure the Location -Go to the Admin Panel, create a `Location` for the project and map the project to the name `pong`. -:::info[link] -https://localhost:3000/admin . +Go to the Admin Panel page, create a `Location` for the project and change the project's name to `pong`. +:::note[Admin Panel] +https://localhost:3000/admin ::: :::important -Ethereal Engine must be running for this step and the rest of this guide. +Ethereal Engine must be running for this step and the rest of this guide to work. ::: -4) Run the project on the web by visiting it with the URL you created. See https://localhost:3000/location/pong +### 4. Run Pong +Run the project on the web by visiting it with the URL you just created. +:::note[Pong] +https://localhost:3000/location/pong +::: ## Understanding Pong -### Quick Overview +### High Level Overview A Pong world can have several separate pong tables at once. Each pong table has four pong plates and can handle four players at once. -Pong has several files which represent parts of the experience: +Pong has several files which represent different parts of the experience: - **PaddleSystem**: A paddle 'system' for managing the player paddles. - **PlateComponent**: Each pong game has one or more plates that represent player positions in a game. This example creates 4 player games. - **PongComponent**: Each pong game in has the concept of a game starting or stopping; this is managed here. @@ -102,7 +108,7 @@ We will separate the 'what' from the 'how' of the paddles: - **how**: The paddles happen to be 3D objects with collision hulls. _(Components)_ > We will get into the details of how this works later. -:::info[advanced] +:::info Ethereal Engine uses the React pattern of allowing state observers to 'react' to state changes. You can read more in depth about this in the [ECS section](/docs/manual/developer/ecs) of the manual. ::: @@ -124,23 +130,27 @@ export const PaddleState = defineState({ ... ``` -:::info[advanced] +:::info The `defineState()` method registers a collection of Record objects. A `Record` is a schema in a third party runtime schema definition language that Ethereal Engine uses. ::: ### PaddleSystem: Introduction to Event Sourced State Ethereal Engine uses an event sourced state paradigm. -Sourcing state and responding to that state is asynchronous but a single 'effect' or outcome results, rather than having to propagate potentially thousands of successive state changes. :::info A good discussion about Event Sourcing can be found here: https://domaincentric.net/blog/event-sourcing-snapshotting ::: +Sourcing state and responding to that state is asynchronous. +But, rather than having to propagate potentially thousands of successive state changes, a single 'effect' or outcome will result from this process. + +In an Event Sourced system, the current state of an aggregate is usually reconstituted from the full history of events. +This means that, before handling a command, we need to do a full read of a single fine-grained stream and transport the events over the network. -In an Event Sourced system, the current state of an aggregate is usually reconstituted from the full history of events. It means that before handling a command we need to do a full read of a single fine-grained stream and transport the events over the network. This allows late joiners to synchronize with the overall game state. +This design allows late joiners of the game to synchronize with the overall game state. -In `PaddleSystem.ts` we define a set of actions explicitly like so: +We define a set of actions explicitly in `PaddleSystem.ts`: ```ts export class PaddleActions { static spawnPaddle = defineAction({ @@ -154,8 +164,8 @@ export class PaddleActions { } ``` -And we then allow the registration of 'receptors' on state objects to catch dispatched events over the network, and in this case we're entirely focused on updating the state records above: - +We then allow the registration of 'receptors' on state objects to catch dispatched events over the network. +In this case, we're entirely focused on updating the state records defined above: ```ts ... receptors: [ @@ -178,15 +188,17 @@ And we then allow the registration of 'receptors' on state objects to catch disp ] }) ``` +`WorldNetworkAction.destroyObject` is an observer that we have injected to make sure that we update our state tables appropriately. -The WorldNetworkAction.destroyObject is an observer we've injected here to catch here to make sure that we update our state tables appropriately. Although we have custom state on the object creation, we don't have any custom state on paddle destruction. +Although we have custom state on the object creation, we don't have any custom state on paddle destruction. -### PaddleState: Introduction to Components -With the state management out of the way, now we're left with the details of making sure our visual representations reflect our state. +### PaddleState: Introduction to Components +With the state management out of the way, we are now left with the details of making sure that our visual representations reflect our state. -PaddleReactor defines a React component that has a useEffect() to observe state changes on a given PaddleState entry. When the PaddleState changes it sets up an entity to reflect that owner. Inside the useEffect() we see several typical 3d and game related components being setup: +`PaddleReactor.ts` defines a React component that has a `useEffect()` to observe state changes on a given `PaddleState` entry. The reactor sets up an entity to reflect that owner when PaddleState changes. +Inside `useEffect()` we see several typical 3D and game related components being setup: - UUIDComponent - TransformComponent - VisibleComponent @@ -197,19 +209,26 @@ PaddleReactor defines a React component that has a useEffect() to observe state - ColliderComponent - GrabbableComponent -Most of these components are self descriptive, and this typically reflects the core set of components you'll see in many Ethereal Engine 3d entities that represent objects in a game. +These components reflect the core set of components that you will find in many Ethereal Engine 3D entities used to represent objects in a game. -The GrabbableComponent is notable in that it's a good example of where components are more than just 'state'; they can be used to form an expressive 'vocabulary' of high level intentions. In this case we want the paddle to stay attached to the owner avatar at a specified attachment point. If we didn't have this concept we would have to fire rigid body physics target position events every frame to keep the paddle synchronized with the player. +:::important +The `GrabbableComponent` is notable because it is a good example of components being more than just 'state'. +They can also be used to form an expressive 'vocabulary' of the high level intentions of the developer. + +In this case, we want the paddle to stay attached to the owner's avatar at a specified attachment point. +If we didn't have this concept we would have to fire rigid body physics target position events every frame to keep the paddle synchronized with the player. +::: ### PaddleState: Introduction to Reactors +Both PaddleReactor and Reactor in `PaddleSystem.ts` demonstrate reactivity to state: +- The reactor is updated whenever state changes +- Game entities that exist are a reflection of that larger state. -Both PaddleReactor and Reactor in PaddleSystem demonstrate reactivity to state. The reactor is updated whenever state changes, and the game entities that exist are a reflection of that larger state. ### PaddleState: System - -Tying everything together in PaddleSystem is the PaddleSystem itself. It registers and runs an execute() handler every frame and it also registers the reactor: - -``` +Tying everything together in `PaddleSystem.ts` is the PaddleSystem itself. +It registers and runs an execute() handler every frame and it also registers the reactor: +```ts export const PaddleSystem = defineSystem({ uuid: 'pong.paddle-system', execute, @@ -219,20 +238,15 @@ export const PaddleSystem = defineSystem({ ``` ### PaddleState: Overall Flow +The general flow of Pong is: +1. The execute handler catches and handles PaddleActions using `receiveActions(PaddleState)` +2. PaddleActions respond to network events and applies them to the PaddleState. +3. The reactor executes its process based on changes to PaddleState. -The general flow is like so: - -1) The execute handler catches and handles PaddleActions using ```receiveActions(PaddleState)``` - -2) The PaddleActions respond to network events and applies them to the PaddleState. - -3) The reactor reacts to any state changes on PaddleState. ## PlateComponent - ### PongComponent, PongGameSystem and PongPhysicsSystem - ### Summary From 9bd7bbfd4a97393c2eae1804562e09afc1c314b2 Mon Sep 17 00:00:00 2001 From: "Ivan Mar (sOkam!)" <7308253+heysokam@users.noreply.github.com> Date: Fri, 29 Dec 2023 11:19:43 -0800 Subject: [PATCH 09/10] fix: Typo in the paddlestate section --- docs/01_gettingStarted/03_developer/02_typescript.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/01_gettingStarted/03_developer/02_typescript.md b/docs/01_gettingStarted/03_developer/02_typescript.md index bbbb3a5363b9..7b0aeddce49b 100644 --- a/docs/01_gettingStarted/03_developer/02_typescript.md +++ b/docs/01_gettingStarted/03_developer/02_typescript.md @@ -238,7 +238,7 @@ export const PaddleSystem = defineSystem({ ``` ### PaddleState: Overall Flow -The general flow of Pong is: +The general flow of PaddleState is: 1. The execute handler catches and handles PaddleActions using `receiveActions(PaddleState)` 2. PaddleActions respond to network events and applies them to the PaddleState. 3. The reactor executes its process based on changes to PaddleState. From 8f05ac41fe1e47646980948b84033a3af3fc3643 Mon Sep 17 00:00:00 2001 From: "Ivan Mar (sOkam!)" <7308253+heysokam@users.noreply.github.com> Date: Thu, 4 Jan 2024 17:38:37 -0800 Subject: [PATCH 10/10] fmt: Moved the guide to the Intermediate section as "best practices" --- .../typescript/01_gettingStarted/00_intro.md | 254 +----------------- .../typescript/02_topic1/00_intro.md | 2 - .../developer/typescript/02_topic1/01_file.md | 2 - .../typescript/02_topic1/02_file2.md | 2 - .../typescript/02_topic1/_category_.json | 8 - .../typescript/50_bestPractices/00_intro.md | 55 ++++ .../50_bestPractices/01_understanding.md | 189 +++++++++++++ .../typescript/50_bestPractices/02_step2.md | 5 + .../typescript/50_bestPractices/03_step3.md | 0 .../50_bestPractices/_category_.json | 8 + 10 files changed, 259 insertions(+), 266 deletions(-) delete mode 100644 docs/developer/typescript/02_topic1/00_intro.md delete mode 100644 docs/developer/typescript/02_topic1/01_file.md delete mode 100644 docs/developer/typescript/02_topic1/02_file2.md delete mode 100644 docs/developer/typescript/02_topic1/_category_.json create mode 100644 docs/developer/typescript/50_bestPractices/00_intro.md create mode 100644 docs/developer/typescript/50_bestPractices/01_understanding.md create mode 100644 docs/developer/typescript/50_bestPractices/02_step2.md create mode 100644 docs/developer/typescript/50_bestPractices/03_step3.md create mode 100644 docs/developer/typescript/50_bestPractices/_category_.json diff --git a/docs/developer/typescript/01_gettingStarted/00_intro.md b/docs/developer/typescript/01_gettingStarted/00_intro.md index 7b0aeddce49b..9fd89e26d12c 100644 --- a/docs/developer/typescript/01_gettingStarted/00_intro.md +++ b/docs/developer/typescript/01_gettingStarted/00_intro.md @@ -1,252 +1,2 @@ ---- -sidebar_label: Typescript ---- -# Getting Started with Typescript - - -:::important -This guide teaches some of the Ethereal Engine recommended best practices for developers. -As such, the level of knowledge required will be higher than if it was just an introductory guide. - -_Visit the [Developer Manual](/docs/manual/developer/intro) page for in-depth information about programming with Ethereal Engine._ -::: - -This guide will teach you how to create **Pong**, a multiplayer game built with Ethereal Engine using Typescript. - -## Installation and Running the project -### 1. Install Pong - -Ethereal Engine scans for projects in its `/packages/projects/projects` sub-folder. -We can install Ethereal Engine from scratch and also register a sub project using the following commands: -```bash -git clone https://github.com/EtherealEngine/etherealengine -cd etherealengine -cd packages/projects/projects -git clone https://github.com/EtherealEngine/ee-tutorial-pong -cd ../../../ -``` - -### 2. Run the engine -A fresh install of Ethereal Engine can be run with: -```bash -npm install -npm run dev -``` - -### 3. Configure the Location -Go to the Admin Panel page, create a `Location` for the project and change the project's name to `pong`. -:::note[Admin Panel] -https://localhost:3000/admin -::: - -:::important -Ethereal Engine must be running for this step and the rest of this guide to work. -::: - -### 4. Run Pong -Run the project on the web by visiting it with the URL you just created. -:::note[Pong] -https://localhost:3000/location/pong -::: - -## Understanding Pong - -### High Level Overview -A Pong world can have several separate pong tables at once. -Each pong table has four pong plates and can handle four players at once. - -Pong has several files which represent different parts of the experience: -- **PaddleSystem**: A paddle 'system' for managing the player paddles. -- **PlateComponent**: Each pong game has one or more plates that represent player positions in a game. This example creates 4 player games. -- **PongComponent**: Each pong game in has the concept of a game starting or stopping; this is managed here. -- **PongGameSystem**: A game system for managing each game instance; starting and stopping play and dealing with players. -- **PongPhysicsSystem**: A ball spawner and scoring 'system' for the pong game overall -- **worldinjection**: Connects the project's code with the engine's code. - -### World Injection -Ethereal Engine projects have a `worldinjection.ts` file to connect the project code to with the engine so it can be run _(aka bootstrapping / injection)_. -For Pong this file registers and starts three systems, and does little else: -```ts -import './PaddleSystem' -import './PongGameSystem' -import './PongPhysicsSystem' -export default async function worldInjection() {} -``` - -### Entities, Components and Systems -Ethereal Engine uses an ECS (Entity Component System). -**Entities** in a game are entirely defined by their **Components** or 'capabilities'. -**Systems** drive the state of the entire application over time by running every frame over those components. - -A great benefit of the ECS pattern is that it allows a highly granular 'vocabulary' for designers to express game ideas with. - -:::info -Ethereal Engine currently uses [bitECS](https://github.com/NateTheGreatt/bitECS) for its ECS implementation. -Another great ECS library to look into is [Flecs](https://news.ycombinator.com/item?id=35434374). -::: - -Pong imports three systems via worldInjection. Each one is responsible for a different part of the Pong experience: -1. PaddleSystem -2. PongPhysicsSystem -3. PongGameSystem - -We will look at `PaddleSystem.ts` first. - -### PaddleSystem: Introduction to State and Reactivity -In Pong each active player has two paddles (one for the left hand and one for the right hand), and paddles are spawned or destroyed as players come and go. - -We will use Ethereal Engine's best practices and separate our concerns. -What this means for us, at this level of abstraction, is that we don't yet need to think about how the paddles are manifested. - -We will separate the 'what' from the 'how' of the paddles: -- **what**: There are some set of paddles. _(Entities)_ -- **how**: The paddles happen to be 3D objects with collision hulls. _(Components)_ - > We will get into the details of how this works later. - -:::info -Ethereal Engine uses the React pattern of allowing state observers to 'react' to state changes. -You can read more in depth about this in the [ECS section](/docs/manual/developer/ecs) of the manual. -::: - - -In `PaddleSystem.ts` we can see a good example of the reactive state pattern that Ethereal Engine follows. -The basic idea here is to track the list of active paddles: -```ts -export const PaddleState = defineState({ - name: 'ee.pong.PaddleState', - initial: {} as Record< - EntityUUID, - { - owner: UserID - handedness: 'left' | 'right' - gameEntityUUID: EntityUUID - } - > - ... -``` - -:::info -The `defineState()` method registers a collection of Record objects. -A `Record` is a schema in a third party runtime schema definition language that Ethereal Engine uses. -::: - -### PaddleSystem: Introduction to Event Sourced State -Ethereal Engine uses an event sourced state paradigm. - -:::info -A good discussion about Event Sourcing can be found here: -https://domaincentric.net/blog/event-sourcing-snapshotting -::: -Sourcing state and responding to that state is asynchronous. -But, rather than having to propagate potentially thousands of successive state changes, a single 'effect' or outcome will result from this process. - -In an Event Sourced system, the current state of an aggregate is usually reconstituted from the full history of events. -This means that, before handling a command, we need to do a full read of a single fine-grained stream and transport the events over the network. - -This design allows late joiners of the game to synchronize with the overall game state. - -We define a set of actions explicitly in `PaddleSystem.ts`: -```ts -export class PaddleActions { - static spawnPaddle = defineAction({ - ...WorldNetworkAction.spawnObject.actionShape, - prefab: 'ee.pong.paddle', - gameEntityUUID: matchesEntityUUID, - handedness: matches.literals('left', 'right'), - owner: matchesUserId, - $topic: NetworkTopics.world - }) -} -``` - -We then allow the registration of 'receptors' on state objects to catch dispatched events over the network. -In this case, we're entirely focused on updating the state records defined above: -```ts - ... - receptors: [ - [ - PaddleActions.spawnPaddle, - (state, action: typeof PaddleActions.spawnPaddle.matches._TYPE) => { - state[action.entityUUID].merge({ - owner: action.owner, - handedness: action.handedness, - gameEntityUUID: action.gameEntityUUID - }) - } - ], - [ - WorldNetworkAction.destroyObject, - (state, action: typeof WorldNetworkAction.destroyObject.matches._TYPE) => { - state[action.entityUUID].set(none) - } - ] - ] -}) -``` -`WorldNetworkAction.destroyObject` is an observer that we have injected to make sure that we update our state tables appropriately. - -Although we have custom state on the object creation, we don't have any custom state on paddle destruction. - - -### PaddleState: Introduction to Components -With the state management out of the way, we are now left with the details of making sure that our visual representations reflect our state. - -`PaddleReactor.ts` defines a React component that has a `useEffect()` to observe state changes on a given `PaddleState` entry. The reactor sets up an entity to reflect that owner when PaddleState changes. - -Inside `useEffect()` we see several typical 3D and game related components being setup: -- UUIDComponent -- TransformComponent -- VisibleComponent -- DistanceFromCameraComponent -- FrustrumCullComponent -- NameComponent -- PrimitiveGeometryComponent -- ColliderComponent -- GrabbableComponent - -These components reflect the core set of components that you will find in many Ethereal Engine 3D entities used to represent objects in a game. - -:::important -The `GrabbableComponent` is notable because it is a good example of components being more than just 'state'. -They can also be used to form an expressive 'vocabulary' of the high level intentions of the developer. - -In this case, we want the paddle to stay attached to the owner's avatar at a specified attachment point. -If we didn't have this concept we would have to fire rigid body physics target position events every frame to keep the paddle synchronized with the player. -::: - -### PaddleState: Introduction to Reactors -Both PaddleReactor and Reactor in `PaddleSystem.ts` demonstrate reactivity to state: -- The reactor is updated whenever state changes -- Game entities that exist are a reflection of that larger state. - - -### PaddleState: System -Tying everything together in `PaddleSystem.ts` is the PaddleSystem itself. -It registers and runs an execute() handler every frame and it also registers the reactor: -```ts -export const PaddleSystem = defineSystem({ - uuid: 'pong.paddle-system', - execute, - reactor, - insert: { after: PhysicsSystem } -}) -``` - -### PaddleState: Overall Flow -The general flow of PaddleState is: -1. The execute handler catches and handles PaddleActions using `receiveActions(PaddleState)` -2. PaddleActions respond to network events and applies them to the PaddleState. -3. The reactor executes its process based on changes to PaddleState. - - -## PlateComponent - -### PongComponent, PongGameSystem and PongPhysicsSystem - -### Summary - +# Topic 1 +Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis. diff --git a/docs/developer/typescript/02_topic1/00_intro.md b/docs/developer/typescript/02_topic1/00_intro.md deleted file mode 100644 index 9fd89e26d12c..000000000000 --- a/docs/developer/typescript/02_topic1/00_intro.md +++ /dev/null @@ -1,2 +0,0 @@ -# Topic 1 -Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis. diff --git a/docs/developer/typescript/02_topic1/01_file.md b/docs/developer/typescript/02_topic1/01_file.md deleted file mode 100644 index 7db4cab12c85..000000000000 --- a/docs/developer/typescript/02_topic1/01_file.md +++ /dev/null @@ -1,2 +0,0 @@ -# Topic -Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis. diff --git a/docs/developer/typescript/02_topic1/02_file2.md b/docs/developer/typescript/02_topic1/02_file2.md deleted file mode 100644 index 7db4cab12c85..000000000000 --- a/docs/developer/typescript/02_topic1/02_file2.md +++ /dev/null @@ -1,2 +0,0 @@ -# Topic -Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis. diff --git a/docs/developer/typescript/02_topic1/_category_.json b/docs/developer/typescript/02_topic1/_category_.json deleted file mode 100644 index 2faf0119c19e..000000000000 --- a/docs/developer/typescript/02_topic1/_category_.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "label": "Topic1", - "position": 02, - "link": { - "type": "doc", - "id": "developer/typescript/topic1/intro" - } -} diff --git a/docs/developer/typescript/50_bestPractices/00_intro.md b/docs/developer/typescript/50_bestPractices/00_intro.md new file mode 100644 index 000000000000..0fc9858f7433 --- /dev/null +++ b/docs/developer/typescript/50_bestPractices/00_intro.md @@ -0,0 +1,55 @@ +--- +sidebar_label: Typescript +--- +# Typescript: Best Practices Guide + + +:::important +This guide teaches some of the Ethereal Engine recommended best practices for developers. +As such, the level of knowledge required will be higher than if it was just an introductory guide. + +_Visit the [Developer Manual](/docs/manual/developer/intro) page for in-depth information about programming with Ethereal Engine._ +::: + +This guide will teach you how to create **Pong**, a multiplayer game built with Ethereal Engine using Typescript. + +## Installation and Running the project +### 1. Install Pong + +Ethereal Engine scans for projects in its `/packages/projects/projects` sub-folder. +We can install Ethereal Engine from scratch and also register a sub project using the following commands: +```bash +git clone https://github.com/EtherealEngine/etherealengine +cd etherealengine +cd packages/projects/projects +git clone https://github.com/EtherealEngine/ee-tutorial-pong +cd ../../../ +``` + +### 2. Run the engine +A fresh install of Ethereal Engine can be run with: +```bash +npm install +npm run dev +``` + +### 3. Configure the Location +Go to the Admin Panel page, create a `Location` for the project and change the project's name to `pong`. +:::note[Admin Panel] +https://localhost:3000/admin +::: + +:::important +Ethereal Engine must be running for this step and the rest of this guide to work. +::: + +### 4. Run Pong +Run the project on the web by visiting it with the URL you just created. +:::note[Pong] +https://localhost:3000/location/pong +::: diff --git a/docs/developer/typescript/50_bestPractices/01_understanding.md b/docs/developer/typescript/50_bestPractices/01_understanding.md new file mode 100644 index 000000000000..a67d42dc6f92 --- /dev/null +++ b/docs/developer/typescript/50_bestPractices/01_understanding.md @@ -0,0 +1,189 @@ +# Understanding Pong + +## High Level Overview +A Pong world can have several separate pong tables at once. +Each pong table has four pong plates and can handle four players at once. + +Pong has several files which represent different parts of the experience: +- **PaddleSystem**: A paddle 'system' for managing the player paddles. +- **PlateComponent**: Each pong game has one or more plates that represent player positions in a game. This example creates 4 player games. +- **PongComponent**: Each pong game in has the concept of a game starting or stopping; this is managed here. +- **PongGameSystem**: A game system for managing each game instance; starting and stopping play and dealing with players. +- **PongPhysicsSystem**: A ball spawner and scoring 'system' for the pong game overall +- **worldinjection**: Connects the project's code with the engine's code. + +### World Injection +Ethereal Engine projects have a `worldinjection.ts` file to connect the project code to with the engine so it can be run _(aka bootstrapping / injection)_. +For Pong this file registers and starts three systems, and does little else: +```ts +import './PaddleSystem' +import './PongGameSystem' +import './PongPhysicsSystem' +export default async function worldInjection() {} +``` + +## Entities, Components and Systems +Ethereal Engine uses an ECS (Entity Component System). +**Entities** in a game are entirely defined by their **Components** or 'capabilities'. +**Systems** drive the state of the entire application over time by running every frame over those components. + +A great benefit of the ECS pattern is that it allows a highly granular 'vocabulary' for designers to express game ideas with. + +:::info +Ethereal Engine currently uses [bitECS](https://github.com/NateTheGreatt/bitECS) for its ECS implementation. +Another great ECS library to look into is [Flecs](https://news.ycombinator.com/item?id=35434374). +::: + +Pong imports three systems via worldInjection. Each one is responsible for a different part of the Pong experience: +1. PaddleSystem +2. PongPhysicsSystem +3. PongGameSystem + +We will look at `PaddleSystem.ts` first. + +## PaddleSystem: Introduction to State and Reactivity +In Pong each active player has two paddles (one for the left hand and one for the right hand), and paddles are spawned or destroyed as players come and go. + +We will use Ethereal Engine's best practices and separate our concerns. +What this means for us, at this level of abstraction, is that we don't yet need to think about how the paddles are manifested. + +We will separate the 'what' from the 'how' of the paddles: +- **what**: There are some set of paddles. _(Entities)_ +- **how**: The paddles happen to be 3D objects with collision hulls. _(Components)_ + > We will get into the details of how this works later. + +:::info +Ethereal Engine uses the React pattern of allowing state observers to 'react' to state changes. +You can read more in depth about this in the [ECS section](/docs/manual/developer/ecs) of the manual. +::: + + +In `PaddleSystem.ts` we can see a good example of the reactive state pattern that Ethereal Engine follows. +The basic idea here is to track the list of active paddles: +```ts +export const PaddleState = defineState({ + name: 'ee.pong.PaddleState', + initial: {} as Record< + EntityUUID, + { + owner: UserID + handedness: 'left' | 'right' + gameEntityUUID: EntityUUID + } + > + ... +``` + +:::info +The `defineState()` method registers a collection of Record objects. +A `Record` is a schema in a third party runtime schema definition language that Ethereal Engine uses. +::: + +## PaddleSystem: Introduction to Event Sourced State +Ethereal Engine uses an event sourced state paradigm. + +:::info +A good discussion about Event Sourcing can be found here: +https://domaincentric.net/blog/event-sourcing-snapshotting +::: +Sourcing state and responding to that state is asynchronous. +But, rather than having to propagate potentially thousands of successive state changes, a single 'effect' or outcome will result from this process. + +In an Event Sourced system, the current state of an aggregate is usually reconstituted from the full history of events. +This means that, before handling a command, we need to do a full read of a single fine-grained stream and transport the events over the network. + +This design allows late joiners of the game to synchronize with the overall game state. + +We define a set of actions explicitly in `PaddleSystem.ts`: +```ts +export class PaddleActions { + static spawnPaddle = defineAction({ + ...WorldNetworkAction.spawnObject.actionShape, + prefab: 'ee.pong.paddle', + gameEntityUUID: matchesEntityUUID, + handedness: matches.literals('left', 'right'), + owner: matchesUserId, + $topic: NetworkTopics.world + }) +} +``` + +We then allow the registration of 'receptors' on state objects to catch dispatched events over the network. +In this case, we're entirely focused on updating the state records defined above: +```ts + ... + receptors: [ + [ + PaddleActions.spawnPaddle, + (state, action: typeof PaddleActions.spawnPaddle.matches._TYPE) => { + state[action.entityUUID].merge({ + owner: action.owner, + handedness: action.handedness, + gameEntityUUID: action.gameEntityUUID + }) + } + ], + [ + WorldNetworkAction.destroyObject, + (state, action: typeof WorldNetworkAction.destroyObject.matches._TYPE) => { + state[action.entityUUID].set(none) + } + ] + ] +}) +``` +`WorldNetworkAction.destroyObject` is an observer that we have injected to make sure that we update our state tables appropriately. + +Although we have custom state on the object creation, we don't have any custom state on paddle destruction. + + +## PaddleState: Introduction to Components +With the state management out of the way, we are now left with the details of making sure that our visual representations reflect our state. + +`PaddleReactor.ts` defines a React component that has a `useEffect()` to observe state changes on a given `PaddleState` entry. The reactor sets up an entity to reflect that owner when PaddleState changes. + +Inside `useEffect()` we see several typical 3D and game related components being setup: +- UUIDComponent +- TransformComponent +- VisibleComponent +- DistanceFromCameraComponent +- FrustrumCullComponent +- NameComponent +- PrimitiveGeometryComponent +- ColliderComponent +- GrabbableComponent + +These components reflect the core set of components that you will find in many Ethereal Engine 3D entities used to represent objects in a game. + +:::important +The `GrabbableComponent` is notable because it is a good example of components being more than just 'state'. +They can also be used to form an expressive 'vocabulary' of the high level intentions of the developer. + +In this case, we want the paddle to stay attached to the owner's avatar at a specified attachment point. +If we didn't have this concept we would have to fire rigid body physics target position events every frame to keep the paddle synchronized with the player. +::: + +## PaddleState: Introduction to Reactors +Both PaddleReactor and Reactor in `PaddleSystem.ts` demonstrate reactivity to state: +- The reactor is updated whenever state changes +- Game entities that exist are a reflection of that larger state. + + +## PaddleState: System +Tying everything together in `PaddleSystem.ts` is the PaddleSystem itself. +It registers and runs an execute() handler every frame and it also registers the reactor: +```ts +export const PaddleSystem = defineSystem({ + uuid: 'pong.paddle-system', + execute, + reactor, + insert: { after: PhysicsSystem } +}) +``` + +## PaddleState: Overall Flow +The general flow of PaddleState is: +1. The execute handler catches and handles PaddleActions using `receiveActions(PaddleState)` +2. PaddleActions respond to network events and applies them to the PaddleState. +3. The reactor executes its process based on changes to PaddleState. + diff --git a/docs/developer/typescript/50_bestPractices/02_step2.md b/docs/developer/typescript/50_bestPractices/02_step2.md new file mode 100644 index 000000000000..d7416984bbcf --- /dev/null +++ b/docs/developer/typescript/50_bestPractices/02_step2.md @@ -0,0 +1,5 @@ +# PlateComponent + +## PongComponent, PongGameSystem and PongPhysicsSystem + +## Summary diff --git a/docs/developer/typescript/50_bestPractices/03_step3.md b/docs/developer/typescript/50_bestPractices/03_step3.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/docs/developer/typescript/50_bestPractices/_category_.json b/docs/developer/typescript/50_bestPractices/_category_.json new file mode 100644 index 000000000000..caf587abc223 --- /dev/null +++ b/docs/developer/typescript/50_bestPractices/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Best Practices", + "position": 50, + "link": { + "type": "doc", + "id": "developer/typescript/bestPractices/intro" + } +}