3mcd/javelin

Limited content for a component

Closed this issue · 4 comments

Came across your lib while looking for a typescript ECS framework for my game.
It looks quite clean and simple to use.

I see in the doc that components must be defined with a schema.
The datatypes look quite poor in terms of what can be in a component: number, boolean, string & array.
I'd like to store much more complex datatypes in my components e.g. map, pathfinder, camera, scene, sprites, etc.
Can this be achieved with this library?

Great lib otherwise! Pretty cool to have something built for a multiplayer game (even though I'm only planning on using it for a single player game all running in the browser)

3mcd commented

Hey @hwaterke, thanks so much for checking out the library and leaving some feedback!

I have a dynamic type that should support this use case in a separate branch. Currently it assumes a type of unknown but I should be able to make it generic so you can register a type like:

const Player = {
  sprite: dynamic<Pixi.Sprite>()
}

You can see the type used in this branch in Javelin's first (and maybe last) built-in component type, ChangeSet, which provides the means to store changes made to components.

dynamic will likely not be supported in the network protocol so when it's available it will be best to use it strategically if you plan on adding multiplayer support to your game.

Another (my preferred) way of dealing with external, non-serializable state is to store it in an effect. For example, you could create an effect which defines a Map<Entity, Pixi.Sprite>.

const useEntitySprites = createEffect(() => {
  const sprites = new Map<Entity, Pixi.Sprite>()
  return () => {
    // create pixi sprites for new players
    onInsert(qryPlayers, e =>
      sprites.set(e, new Pixi.Sprite()),
    )
    // destroy pixi sprites for removed players
    onDestroy(qryPlayers, e =>
      sprites.delete(e),
    )

    return sprites
  }
}, { global: true })

Then, if you need a reference to the sprite in a system, you can call the effect to get a reference to the sprites map, and lookup the sprite by entity directly:

const sysRender = () => {
  const sprites = useEntitySprites()
  // sync player position to pixi sprite
  for (const [entities, players] of qryPlayers) {
    for (let i = 0; i < entities.length; i++) {
      const player = players[i]
      const sprite = sprites.get(entity)
      sprite.position.x = player.x
      sprite.position.y = player.y
    }
  }
}

Where you put the logic for creating/deleting the sprites is arbitrary. It can be in the system or in the effect. I prefer to place any synchronization logic (i.e. logic that could be used in any game, not just your own) in the effect, and game-specific logic in the system.

You can see #121 for some more discussion around this topic.

Hey @3mcd,

Thanks for the quick and detailed answer.

The effect part will definitely solve the issue until the dynamic type is released.
I feel that it adds a lot of complexity though as it looks like it does the same job as a component in this case. So part of the state is in components and some is in effects. I guess it makes sense because of the whole multiplayer/client-server aspect which definitely adds interesting challenges like serialisable components, etc.
Is that why Effects exist in you library or are there other reasons to not only stick to components and systems?
I feel like it's probably overkill for my needs of a local ecs running in the browser but I'm still curious about what you call the synchronous/serializable model

Anyway, that being said, @javelin/ecs looks like the most promising ECS lib that I could find. I'm in the process of migrating from https://ecsy.io/ as it does not seem to be maintained anymore and the lib was not typescript-first.

3mcd commented

The effect part will definitely solve the issue until the dynamic type is released.

I'm glad it can help you out in the interim!

The 1.0 release that will include dynamic will also contain a few API changes, so you'll need to do an extra migration step after you're done migrating from ECSY to get to 1.0. Most of the changes are associated with networking, and the API changes to @javelin/ecs are fairly minor (e.g., world.component() becomes component(), query() becomes createQuery(), etc.). I plan on writing a short migration guide to help folks move to 1.0.

I feel that it adds a lot of complexity though as it looks like it does the same job as a component in this case. So part of the state is in components and some is in effects. I guess it makes sense because of the whole multiplayer/client-server aspect which definitely adds interesting challenges like serialisable components, etc.

You're totally right. Storing third-party objects outside of the ECS is really just an opinionated practice of mine. I'll make sure to improve the section about this in the docs, and add a new section specifically for including third-party objects with dynamic.

Is that why Effects exist in you library or are there other reasons to not only stick to components and systems?

Effects were introduced primarily to handle side-effects within systems (like HTTP requests, simple timers, loading assets, etc).

I feel like it's probably overkill for my needs of a local ecs running in the browser but I'm still curious about what you call the synchronous/serializable model

"synchronous/serializable" is total jargon and should be clarified or removed.

Anyway, that being said, @javelin/ecs looks like the most promising ECS lib that I could find. I'm in the process of migrating from https://ecsy.io/ as it does not seem to be maintained anymore and the lib was not typescript-first.

Awesome, let me know if you need any help migrating, or have any other suggestions for the library!

3mcd commented

The concerns raised in this issue have been addressed in 1.0 (currently alpha) with the new dynamic type and support for third-party/external objects.