Giter Club home page Giter Club logo

javelin's People

Contributors

3mcd avatar dependabot[bot] avatar drsensor avatar github-actions[bot] avatar ithamar avatar kenjinp avatar notrueblood avatar rjmontgo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

javelin's Issues

"not" filters

It would be useful to have a query filter that tests for the absence of a component. For example, I'm experimenting with maintaining an entity hierarchy datastructure and using components to declare the hierarchy and a system to enforce it. For this system, I'd like any entity without a Child component to be considered a 'root' entity and added to the root node directly. To do so I'll need to filter by entities without the Child component (not just detached, as they never had it) to add them to the tree.

I don't think this can be implemented as a custom filter as it appears that custom filters require the presence of their topic Component type, which is obviously incompatible with a not filter.

Register system after World is instantiated

Currently systems are registered via the createWorld factory:

const world = createWorld([systemA, systemB]);

It would be useful to be able to add a system after the world is instantiated:

world.addSystem(systemC);

Proposal: default component initializer

Defining component initializers can be a little tedious. It seems that with the schema information, it should be possible to provide a default component initializer with full typing support:

const defaultComponentInitializer<S extends Schema> = (component: ComponentType<S, any>, props: PropsOfSchema<S>) => {
  Object.assign(component, props);
};

... or similar, such that initializing a component without a user-defined initializer is simply

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

world.spawn(world.component(Position, { x: 0, y: 3, z: 12 }));

What do you think? I'd happily contribute the code if you agree with the concept.

Question: cache simulation or world state

Is there a lifecycle hook or a mechanism that I can tap into to listen every change in the world?

My case is to cache all entities into localStorage at specific interval so it can resume the simulation when browser accidently closed the tab or crashed.

(maybe something like queryAll() entities ๐Ÿค”, don't know if it's good idea)

Events

I'd like to have an idiomatic way of handling events with Javelin ECS. Pulling off events in a "pure" fashion in ECS is hard.

I propose we pass events as another argument to systems so they can remain as self-contained as possible, without relying on a global/singleton event bus.

Systems can target other systems using a new method, world.dispatch.

Example API:

input.ts

import { physics } from "./physics";

function input(dt: number, world: World) {
  const playerEntity = getPlayerEntity(world);

  if (keyboard.space) {
    world.dispatch(physics, { type: "apply_force", entity: playerEntity, force: [0, 0, 10] });
  }
}

physics.ts

type PhysicsEvent = { type: "apply_force", entity: number, force: Vector3 }

const engine = new PhysicsEngine();
const bodies = new Map<number, PhysicsEngineBody>();

function physics(dt: number, world: World, events: PhysicsEvent[]) {
  events.forEach(e => {
    if (e.type === "apply_force") {
      const body = bodies.get(e.entity);
      engine.applyForce(body, e.force);
    }
  });
  // ...
}

Network Filtering

Per-client component filtering can be done by managing a separate MessageProducer for each connected client. However, there is no way to narrow the components any further than by their type, so all components that match the type field are eligible for the next reliable/unreliable update.

I'd like to have a way to only select components that meet a certain criteria, e.g.

function createMessageProducerForClient(client: Client) {
  return createMessageProducer({
    components: [
      // always send this client's position
      {
        query: query(playerOfClient(client)(Player), Position),
        select: [Position],
        priority: Infinity
      },
      // send dynamic (moving) positions most of the time
      {
        query: query(dynamic(Position)),
        select: [Position],
        priority: 100
      },
      // send static positions only when they change (reliably)
      {
        query: query(static(Position)),
        select: [Position]
      },
    ]
  })
}

Detecting when an entity is included or excluded from a query

I'm currently looking into integration of Javelin ECS with React, specifically react-three-fiber. I saw #104 , which was a helpful starting point - however there are some significant performance concerns around calling setState every game step. React reconciliation begins to become a bottleneck.

I'd like to set up a collection of hooks which help organize a tree of React components to render the entities of my game, including utilization of observed component changes to strategically update components which don't change as frequently. To that end my goal is to create at least the following:

function CharacterRenderer() {
    // this hook keeps an updated list of entity IDs which match the provided
    // components. it doesn't trigger re-renders until the entity list changes.
	const entities = useEntities(Character, Position);

    return <>{entities.map(id => <CharacterEntity id={id} key={id} />)}</>;
}

// memoization ensures this component only re-renders when
// internal data changes
const CharacterEntity = React.memo(({ id }) => {
    // this hook just stores a component reference retrieved from World,
    // saving us a lookup each frame. We rely on `useEntities` in the parent
    // to ensure that this component is never rendered with an entity which
    // does not have Position, otherwise an error would throw here
    const position = useComponent(id, Position);
    // this hook automatically re-renders when the observed component is modified
	const character = useObservedComponent(id, Character);

	const ref = useRef();

    // this hook runs every frame (provided by react-three-fiber) and
    // will handle frequently-changing properties like transforms without
    // triggering a re-render at all
    useFrame(() => {
		ref.current.position.set(position.x, position.y, position.z);
    });

    return <mesh ref={ref}>{character.model === 'red' ? <RedGeometry /> : <BlueGeometry />}</mesh>;
});

I believe the useComponent and useObservedComponent hooks could be done today, but I believe in order to create an efficient useEntities, I'll need to have some sort of understanding of when an entity first matches a query, and when it no longer matches the query. An event-based approach might be the most convenient.

A naive approach might be to add a callback to each Archetype for when an entity is added or removed, then have the Queries register callbacks for their Archetypes and emit events when things change. The naive part is that if an Entity is moved between two Archetypes which both match a Query, it would issue the removed and added events immediately after each other - but while it's not ideal it is at least still correct and probably sufficient as a first pass and could probably be corrected with a bit of logic in the Query.

Interoperate with UI library, for example, React

I'm trying to create a thing that works like react-redux to allow react components render things into the canvas (using react-pixi-fiber) based on data inside the ECS world.

The pseudo-code is

import { useContext, cloneElement } from "react"
import { createComponentFilter, query, World } from "@javelin/ecs"

const connect = (component, selector: ReturnType<query>, topic) => (WrappedReactComponent) => {

  const world = useContext(ContextContainsWorld)

  // there will be lots of components of same type to be render, using same React component
  // so we return an array 
  const reactComponentsToRender = []
  for (const [entity, [...data]] of selector(world)) {
    reactComponentsToRender.push(<WrappedComponent data={data} topic={topic} />)
  }
  return (reactOwnProps) => {
    return reactComponentsToRender.map(element => cloneElement(element, reactOwnProps))
  }
}

In this way, we can create a <Pawn /> component, and render 10 Pawn if there are 10 PawnComponent in the ECS world.

Use it like:

import Position from "../ecs/components/position"
import RightClickTopic from "../ecs/topics/rightClick"

function Pawn(props) {
  return // some react-pixi-fiber components
  // ... onClick={() => props.topic.push(message)}  ... 
}

const visible = createComponentFilter<typeof Position>(() => component =>
  component.x >= 0 && component.x <= 800 && component.y >= 0,
)
const culledPositions = query(visible(Position))

export default connect(Position, culledPositions, RightClickTopic)(Pawn)

Do you use react and redux? What do you think?

One possible problem is that I'm not calling query inside a system...
Another problem is that I need to make data immutable, so react will rerender iff data is changed.

Dealing with non-serializable state

Hi there! I've been building my own TypeScript ECS, but I found Javelin and I agree with basically all the implementation decisions, so I'd like to use it!

Javelin does seem to have one opinion I'm having a bit of trouble adjusting to - it appears that all components must be serializable (just based on schema constraints).

I like that a lot in principle. Has a lot of great implications for networking or webworker integration.

I'm curious though, how live state like a physics RigidBody would be best stored, accessed, and disposed.

In other ECS, like ecsy for instance, there's a concept of a "State Component" which lives only at runtime. In my own implementation I had that concept, so to add a RigidBody you might attach a serializable BodyConfig component, and then the System would query BodyConfig, Not(Body) entities and add a Body to those on frame 1 which stores a reference to the actual RigidBody from the physics simulation library.

When the entity is destroyed, or the body should otherwise be removed, the world would remove the BodyConfig component so that it's left with just Body - and the system would have another query for Body, Not(BodyConfig) which disposes the rigidbody and removes the Body.

There are some downsides to that approach (including forgetting to do the cleanup step), and it's a bit obtuse to learn. But it does have nice properties of easily and efficiently associating non-serializable state to an entity utilizing the ECS framework.

How would you recommend dealing with object references like that? I noticed in one of the code examples in the docs you have a getBodyByEntity function - is that the idea, just to store things in global state and lookup using entity IDs?

Release 0.3.3

This release includes the new world.addSystem method to add systems after a world has already been initialized.

Can initialize function have a proper type?

Currently following code will have an any type for the position

import { createComponentType, string, number } from '@javelin/ecs';

export const VisibleItem = createComponentType({
  name: 'VisibleItem',
  type: 1,
  schema: {
    x: number,
    y: number,
    texture: string,
  },
  initialize: (position, x = 0, y = 0) => {
    position.x = x;
    position.y = y;
  },
});

Screen Shot 2020-09-06 at 10 52 44 PM

Is it possible to properly type initialize?: I; using information from the schema: S;?

Question: Why menory usage grow in the Doc example?

In the doc you said

The memory growth (0.3mb) is consistent with standard setInterval or requestAnimationFrame

If you are just iterating, not adding any new components, why there need new memory, what is allocated into new memory?

Automatic ID generation

Might be the second of a couple of issues I'll file as I begin digging into the library.

How do you feel about automatically assigning an incrementing ID to components? It can be a bit of a pain to have to manually manage something which would be pretty trivial for the computer to track.

Since components are already registered with the World, that seems like it would be a good place to automatically provide it a unique ID. That way you could conceivably even import third-party components from libraries, register them to the World, and their IDs would be guaranteed unique with your own components. Right now it would be difficult to pull in exotic components and be confident your manually selected IDs are not colliding with theirs.

Question: Can I store entity id in a component?

I'm storing tilemap data using the Tile component, and treat them as a list to render them, this works fine.

But if I want to run A* routing algorithm on the tilemap, I have to treat them as 2D array again, but the entity list is a 1D array now...

So I'd like to put a number field in the component to store 4 entity ids, which is in the NSWE of this entity.
So in the map system, I can use this information to reconstruct the 2D tilemap array.

Is this approach OK?

Developer Tools

For a 1.0 release, we should have a developer tool that a user can embed into a web application to debug local and remote ECS instances. At a bare minimum, the tool would receive snapshots of the ECS state at regular intervals.

0.19.3

Creating release for 0.19.3

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.