Skip to content

v1.0.0-alpha.0

Pre-release
Pre-release
Compare
Choose a tag to compare
@3mcd 3mcd released this 16 May 19:17
· 80 commits to master since this release

1.0.0 alpha is here! This update brings a lot of improvements to Javelin's API, performance, and networking utilities. Below is a high level summary of the changes that I hope to outline in a blog post or on the website as we get closer to a full release.

API Changes

  • Built-in effects are now prefixed with use instead of eff. For example, effMonitor becomes useMonitor.
  • query -> createQuery
  • world.component -> component
  • array -> arrayOf

Component Types

Component types are now defined as simple objects, e.g.,

const Position = createComponentType({
  type: 1,
  schema: {
    x: number,
    y: number,
  },
})

becomes the following:

const Position = {
  x: number,
  y: number,
}

A component type (schema) can be registered with a specified type id (integer) using the registerSchema function:

import { registerSchema } from "@javelin/ecs"
registerSchema(Position, 99)

Monitors

Triggers were removed and monitors were made a bit more flexible. useMonitor now provides an array of changed components that caused a transition to happen.

const q = createQuery(A, B, C)
useMonitor(
  q,
  (entity, [a, b, c]) => { /* component a, b, or c is non-null if it was attached */ },
  (entity, [a, b, c]) => { /* component a, b, or c is non-null if it was detached */ },
)

Queries

query.forEach was removed in favor of a slightly simpler syntax for iteration. To iterate a query using callbacks, simply invoke the query as a function:

q.forEach((e, [a, b, c]) => {})

becomes the following:

q((e, [a, b, c]) => {})

Networking

The entire @javelin/net package was rewritten. Not only does the protocol now have a built-in, efficient ArrayBuffer protocol, users will now have more control of how network messages are sent. MessageProducer and MessageHandler both remain but were also rewritten to support the new networking model. MessageProducer has slightly different responsibilities now, as it will not automatically generate messages with a declarative configuration; it will instead expose an imperative API over the lower-level protocol functions but handle common patterns, like sorting patches by priority and partitioning messages by maximum length.

Below is an example of how MessageProducer is used. MessageHandler remains largely unchanged.

import { createMessageProducer, encode, decode } from "@javelin/net"
const producer = createMessageProducer({ maxByteLength: 1000 })
const a = component(A)
const b = component(B)
producer.spawn(1, [a])
producer.attach(1, [b])
producer.detach(1, [a])
producer.update(1, [{...a, x: 1}])
// etc.

const message = producer.take(true /* include component model? */)
const encoded = encode(message)

decode(message, {
  onSpawn(entity, components) {
    // entity = 1, components = [a]
  }
})

@javelin/pack views are now supported inside of component schemas. If you're building a multiplayer game, consider using views over number to save bytes:

import { uint8 } from "@javelin/pack"
const Fighter = {
  attackMode: uint8, // saves you 7 bytes over `number`!
}

Change Detection

The current strategy of change detection using Proxies is just too slow. After a lot of trial and error, I came to the conclusion that both getters/setters and proxies are both too slow for the types of games I would expect Javelin to support (games with hundreds to thousands of dynamic, networked entities). So, similar to the MessageProducer rewrite, control is given back to the user for detecting changes and generating patches.

Since change tracking is optional and kind of an advanced feature, its implementation was split out into a new package named @javelin/pack. Below is an example of the API:

import { ChangeSet, set } from "@javelin/track"
const entity = world.spawn(
  component(Position),
  component(ChangeSet),
)
const query = createQuery(Position, ChangeSet).bind(world)
const [position, changes] = query.match(entity)
set(position, changes, "x", 1)
set(position, changes, "y", 2)
// then, to write the patch to a message...
producer.patch(entity, changes)

Thanks for reading, and if you get a chance to test the new release, please open an issue with any feedback or issues you run into!!