Coder Social home page Coder Social logo

jaimegensler / thyseus Goto Github PK

View Code? Open in Web Editor NEW
73.0 3.0 3.0 1.38 MB

An archetypal Entity Component System, built entirely in Typescript

License: MIT License

TypeScript 95.75% JavaScript 4.25%
archetype ecs entity-component-system javascript typescript js ts multithreaded

thyseus's Introduction

npm version license: mit pull requests: welcome code style: prettier

Thyseus is a DX & performance oriented archetypal Entity Component System (ECS) for Typescript. It provides an expressive, type-driven API so you can focus on how data moves through your app instead of worrying about how to manage it. Many features are included out of the box, including:

  • Effortless integration with third-party libraries like three.js.
  • Archetypal storage for lean memory use and cache-friendly iteration.
  • Complex queries with filters like Maybe, And, Or, With, and Without.
  • First class support for Resources (singletons) and Events.
  • Boilerplate-free and safety-first worker thread support - no eval()!
  • Deeply customizable execution logic for easy handling of patterns like fixed updates.

Get started with the docs, or join us on the Web-ECS Discord!

Installation

# pnpm
pnpm add thyseus

# yarn
yarn add thyseus

# npm
npm i thyseus

Contributing

If you're interested in contributing, please have a look at the code of conduct and the contributing guide first.

API

Components

class Vec2 {
  x: number;
  y: number;
  constructor(x = 0, y = 0) {
    this.x = x;
    this.y = y;
  }

  add(other: Vec2) {
    this.x += other.x;
    this.y += other.y;
  }
}
export class Position extends Vec2 {}
export class Velocity extends Vec2 {}

Systems

import { Query } from 'thyseus'
import { Position, Velocity } from './components';

export function spawnEntities(commands: Commands) {
  commands.spawn().add(new Position()).add(new Velocity(1, 2));
}

export function move(query: Query<[Position, Velocity]>) {
  for (const [pos, vel] of query) {
    pos.add(vel);
  }
}

Worlds

import { World, Schedule } from 'thyseus';
import { moveSystem, spawnEntitiesSystem } from './systems';

class SetupSchedule extends Schedule {}

export const myWorld = await new World()
  .addEventListener('start', async world => {
	await world.runSchedule(SetupSchedule);
	function loop() {
		await world.runSchedule(Schedule);
		requestAnimationFrame(loop);
	}
	loop();
  })
  .addSystems(SetupSchedule, spawnEntities)
  .addSystems(Schedule, move)
  .prepare();

myWorld.start();

thyseus's People

Contributors

3mcd avatar jaimegensler avatar kayhhh 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

Watchers

 avatar  avatar  avatar

Forkers

3mcd haydermabood

thyseus's Issues

[FEAT] Add dev-only warning for no applyCommands systems

Describe the problem this feature solves

I make the dadgum library and I forget to add it and spend 5 minutes wondering why all my queries are empty. I think it's right to leave it out by default but we should at least throw a warning in the console so it's quicker to figure out what's going on.

Describe the solution you'd like to see

DEV_WARN to pair with DEV_ASSERT, warn when no schedules contain applyCommands.

[BUG] Without filters not working

Bug Description

Looks like Without filters aren't really working?

Thyseus Version

0.16.0

Bundler / Build Tool

Vite

Environment & Version

N/A

Reproduction Repo (optional)

[FEAT] [BRK] Simplify parameter map

Describe the problem this feature solves

Using custom parameters with the transformer right now is tedious, as you have to provide an absolute import path. As the global import doesn't exist anymore, it'd be nicer to just assume the parameter type is in scope. We also don't need to map names anymore as we're introducing the notion of pass-thru parameters (like Readonly<T>).

New parameter map can just be a Record<string, boolean>, where the key is the name of the type to recognize, and boolean is if we pass act on it (true for most, false for pass-thru).

This will significantly improve the ergonomics of custom system parameters with the transformer

[FEAT] Enum components

Describe the problem this feature solves

Rust-like enum components would be a nice win.

Potential Solutions

I've listed out a few options here - currently leaning towards (3) as the most likely ideal approach

Solution 1 (Association Enums)

Each enum variant is its own class and the enum type is a union of the variants. We can "associate" these distinct classes as enums by setting a custom static property and reading it to determine component type (as opposed to just looking at object.constructor).

// Defining
class Pending {}
class Resolved {
	value: any;
}
class Rejected {
	error: any;
}
type Promiseish = Pending | Resolved | Rejected;
// This function mutates these classes to make them associated
const Promiseish = Enum({ Pending, Resolved, Rejected });
const instance = new Promiseish.Pending();

// In use
function mySystem(query: Query<Promiseish>) {
  for (const promiseish of query) {
    console.log(typeof promiseish); // Could be Promiseish or any subtype!
  }
}

Pros:

  • Localizes enum definition to one place
  • Pretty small API
  • Enums look like normal components mostly
  • Direct handling of enum instances

Cons:

  • Must define enums twice (once as a type, once as an object)
  • Enum() mutates the classes given to it
  • Columns will store objects of variable shape, queries could deopt because promiseish is multiple kinds of object

Solution 2 (Wrapper Enums)

Enums as wrappers with a type and data field:

class Promiseish extends Enum({ Pending, Resolved, Rejected }) {}
const instance = new Promiseish(new Pending());
instance.type; // Pending
instance.data; // new Pending() created above

Pros:

  • Pretty straightforward

Cons:

  • Twice as many objects
  • Don't get to handle enum objects directly (i.e. always thru .data)
  • Kind of curious how VMs handle objects with references to other objects with different shapes - no idea on what this looks like for perf, if it even matters.

Solution 3 (Extension Enums)

This isn't exactly enums but it achieves a similar result. Rework how archetypes are modeled (we probably have to do this anyway) and allow filters/modifiers to define a matching function. Expose an Extended<T> type (would come up with a better name) that matches subclasses and not just the exact class. This could have uses beyond just enum-like behavior or the Extended<T> type

// Defining
class Promiseish {}
class Pending extends Promiseish {}
class Resolved extends Promiseish {
  data: any;
}
class Rejected extends Promiseish {
  error: string;
}

// In use
function mySystem(query: Query<Extended<Promiseish>>) {
  for (const promiseish of query) {
    console.log(promiseish.constructor); // Could be Promiseish or any subtype!
  }
}

Pros:

  • Direct object handling
  • Single definition of "union" type
  • Table columns are single-shape
  • Extensible enums! e.g. a physics library could provide a Collider type and just require consumers to extend the class with an onCollide() method. Library systems can then query for consumer components

Cons:

  • Changing enum type is a table move (does this happen much?)
  • Always have to specify Extended<T> in queries (we'd make this shorter, but still)
  • Queries still have de-opt potential
  • Extensible enums (hah)

[FEAT] `Query.p.groups()`/`Query.p.pairs()`

Describe the problem this feature solves

Double-iterating queries (and possibly deeper?) is a fairly common pattern, but doing it in a sane way isn't super feasible.

If we want to avoid double comparisons, this is the best way to do it right now:

function detectCollisions(query: Query<Collider>) {
  let i = 0;
  for (const collider of query) {
    let j = 0;
    for (const collider of query) {
      if (i <= j) {
        continue;
      }
      // Code
    }
  }
}

Describe the solution you'd like to see

A method on queries to enable double (or deeper) iteration. The above could look like either:

function detectCollisions(query: Query<Collider>) {
  for (const [colliderA, colliderB] of query.pairs()) {
  }
}

or

function detectCollisions(query: Query<Collider>) {
  for (const [colliderA, colliderB] of query.groups(2)) {
  }
}

Implementing pairs is easier but less flexible and harder for inlining. Groups is way more flexible but harder to inline.

What alternatives exist? (optional)

It's possible to define a custom pairs() function that abstracts the iteration count tracking, but providing it as a first-party method is probably the right move, and opens it up to eventual iterator inlining

[FEAT] `Maybe<T>` in queries

Describe the problem this feature solves

Optionally query for components if they're present, yield undefined if not.

We could probably just provide an empty array for columns that aren't present in the matched table and rely on OoB access to return undefined

[FEAT] Enter/Exit queries

Describe the problem this feature solves

Queries for when an entity beings matching some conditions, or no longer matches some conditions

Describe the solution you'd like to see

Filters (Enter<T>/ Exit<T>) are one solid option and is how Bevy handles this. I think there's a solid argument to be made that these are conceptually a bit different from normal queries (it's more like an event listener, really), so a new type of query-like system parameter also could make sense.

[FEAT] Async plugins

Describe the problem this feature solves

Allowing plugins to return a promise would probably be helpful in cases where you have async setup to do that isn't part of a system parameter - for example, inserting a resource that requires async setup but doesn't have a fromWorld property.

Describe the solution you'd like to see

Allow plugins to return a promise. Aggregate a promise that's awaited before calling .prepare() on schedules in world.prepare(). addPlugin() can't be async itself as it'd require wrapping every step of world building in await.

Rough implementation idea, should be refined/reconsidered more

class World {
  inProgress: Promise<any> | null
  
  addPlugin(plugin: Plugin): this {
    const result = plugin(this)
    this.inProgress = Promise.all([result, this.inProgress])
  }
  
  prepare(): Promise<this> {
    await this.inProgress;
    this.inProgress = null;
    // ... rest of World.p.prepare(), as it exists today
  }
}

[BUG] Implicit dependencies cause schedules to not complete execution

Bug Description
Title.
The introduction of schedules and requiring applyCommands to be added explicitly reduced much of the need for implicit dependencies. Furthermore, resolving implicit dependencies in a coherent manner gets very complex very quickly, and can have many unintended side-effects. Best resolution will be to remove them.

Thyseus Version (e.g. v0.9.0)
v0.12.1

Bundler / Build Tool (e.g. Vite, Webpack)
All

Environment & Version (e.g. <browser> <version>, Node <version>)
All

Reproduction URL (optional)
N/A

[BUG] Initial struct values 0 on Res & SystemRes

Bug Description

The initial values when defining a struct are not set correctly for Res and SystemRes

import { World, StartSchedule, DefaultSchedule, struct, Res } from 'thyseus';

function start(world: World) {
	async function loop() {
		await world.runSchedule(DefaultSchedule);
		requestAnimationFrame(loop);
	}
	loop();
}

@struct
export class TestResource {
    value: number = 2;
}

export function systemExample(test: Res<TestResource>) {
	console.log(test.value)
}

const world = await World.new()
	.addSystemsToSchedule(StartSchedule, start)
	.addSystems(systemExample, /* Your systems here! */)
	.build();

world.start();

Console logs out 0

Thyseus Version (e.g. v0.9.0)**

0.14.0

Bundler / Build Tool (e.g. Vite, Webpack)**

Vite

Environment & Version (e.g. <browser> <version>, Node <version>)**

Brower

Reproduction URL (optional)**

[BRK] Remove `Read<T>`, `ReadModifier`

Describe the problem this feature solves

Read<T> is neat but kind of complex and moves away from more idiomatic TS. We could re-add it in the future but it's just not really necessary at the moment I feel.
Also can update transformer config to allow pass-thru types that are simply ignored, so Readonly<T> can just be ignored essentially.

[FEAT] Introduce thread identity

Currently it's only possible to tell if the current thread is the main thread or not. We should expose some notion of thread identity - probably as an id instance member of ThreadGroup and a way to send messages to individual workers.

I don't think this needs to be much fancier than ThreadGroup creating a pointer to a u8 and threads atomically incrementing the number at that pointer. ID collision between worlds shouldn't really be a concern, as the main thread is the main thread (id 0) and I can't think of a use case where one world's workers would need to have a unique id from another world's workers.

[HELP] Possibility to share system/components

What do you need help with?

Since thyseus is a "compiled" ecs framework (@thyseus/rollup-plugin-thyseus), I wonder if it is possible to share a single system/component as an ESM file (to load in the browser) or a cjs file (to load in server side).
Or does each game need to "compile from source"?

Usecase is for modding, I want my player can write some system/component/data too, and I hot reload the world with user-provided JS files (in a sandbox so I can just eval them).

Target Environment

Browser, Node, Bun

[BUG] Using addType doesn't always copy data correctly

Depending on order of components, data may not be copied correctly ๐Ÿ˜ฌ

Thyseus Version (e.g. v0.9.0)
0.12.0

Bundler / Build Tool (e.g. Vite, Webpack)
All

Environment & Version (e.g. <browser> <version>, Node <version>)
All

Reproduction URL (optional)

Bug Description

[CLEANUP] Investigate emplace pattern

We use a fetch-or-create pattern pretty much everywhere - resources, events, threads, components. The exact way we do this is a little different everywhere, too.

Ideally, we could make a helper function for this that takes away a lot of the hassle (like the emplace proposal). At the very least, we should unify around a common pattern of what this code looks like.

[FEAT] States

Describe the problem this feature solves

Bevy has a first-party resource for states that would be helpful and likely pair nicely with run conditions.
Useful for patterns like pause states, main menus, etc.

Maybe dependent on enums?

[FEAT] `Query.p.single()`

Describe the problem this feature solves

Similar to the query.get() proposal, a query.single() API might be a nice addition for unique entities.

Describe the solution you'd like to see

Query.p.single()method that returns the requested data for the entity that matches. Probably we just throw if 0 or multiple entities matches the query

function mySystem(query: Query<Position, With<IsPlayer>>) {
  const position = query.single();
}

[BUG] No-op commands cause crashes

Commands that should be no-ops can cause tabs to crash.

Thyseus Version (e.g. v0.9.0)

Bundler / Build Tool (e.g. Vite, Webpack)

Environment & Version (e.g. <browser> <version>, Node <version>)

Reproduction URL (optional)

Bug Description

[BUG] Typescript return type for vite in rollup-plugin-thyseus incorrect

Bug Description

When viewing vite plugin config. The Thyseus() return type is being flagged as an incompatible type. From looking at other rollup vite compatible plugins. Most seem to use return type Plugin from 'rollup'

Thyseus Version

HEAD

Bundler / Build Tool

Vite, pnpm

Environment & Version

Vscode

Reproduction Repo (optional)

Just create a new folder with the create thyseus template

[FEAT] Cleaner query filter API

Query filtering gets nasty pretty quickly when you need queries of any depth/complexity. Also, tuples to represent And filters sound nice in theory but are just less readable than the alternatives. The current API should be reworked to be cleaner and more readable.

Proposed API

The new API should introduce explicit And and remove tuples. Additionally, we can allow more generic arguments to be passed to And, Or, With, and Without - likely 4 to start, though this could be extended in the future. This should dramatically reduce the need for nesting and be significantly easier to parse at a glance.

This allows filtering for multiple components to be expressed with a simple With<A, B> rather than And<With<A>, And<With<B>>, but does not provide the same ergonomics for Or filters. This is intentional - And filtering adds to one possible condition and further restricts results, while Or filters create new branches of possible matches. As a result, overly-ergonomic Or filters would likely end up harder to parse by making it more difficult to determine all the possible combinations that could match.

Examples

// As before!
function mySystem(
  query: Query<[], With<A>
) { }

// Has all of A, B, C, & D
function mySystem(
  query: Query<[], With<A, B, C, D>
) { }

// Has none of A, B, C, & D
function mySystem(
  query: Query<[], Without<A, B, C, D>
) { }


// Has A, does not have B, and either has C or does not have D
function mySystem(
  query: Query<[], And<With<A>, Without<B>, Or<With<C>, Without<D>>>>
) { }

[FEAT] Migrate decorators to new ES Decorators

New impl should be class auto-accessor decorators, but we could open a discussion on also supporting plain getters / setters.

This will be a breaking change that requires consumers to remove "experimentalDecorators" from their tsconfig and use the new accessor keyword instead of declare.

Last I looked into this, there were some issues with support particularly for prettier and some bundlers. If those aren't resolved yet, this may need to be delayed.

[FEAT] SystemBuilder API

Describe the problem this feature solves

We need to provide a way of defining systems that doesn't rely on the transformer or on handwriting system properties.

I accidentally committed some of the toying around with this I did so could be a good starting point ๐Ÿคท

Describe the solution you'd like to see

A systembuilder is probably the ideal way to handle this.
For example:

const mySystem = system()
  .query([Position, Velocity])
  .res(Time)
  .build((query, time) => { /* ... */ })

Must be well typed and extensible - probably configureable by providing an object to the system() function.

const mySystem = system({mySystemParam: MySystemParam })
  .mySystemParam() // Now defined and strongly typed
  .build()

[FEAT] Allow `Entity` objects to be dropped if no longer used

Description

We hold onto Entity objects in the empty table (id 0). We ideally wouldn't do this to allow the objects to be reclaimed by the VM, and we don't recycle them anyway. They can be dropped once an entity has been spawned in their place, but if you spawn a bunch of entities and no longer need that many, you'll have an array of objects that will never be cleaned up.

Either Entity instances should be recycled (a breaking change), or we should allow them to be dropped.

[FEAT] Investigate `Thread<T>` proxies, transferable object API

Describe the problem this feature solves

Proxies may be a cleaner way to handle our threading solution - rather than passing a string name for the function, getting an object that's a proxy with those properties on it would be neat.

Our threading solution also needs some sort of way to handle transferable objects.

Describe the solution you'd like to see

Option 1 (Transferable Wrapper)

This is how threads.js handles this. If you want to transfer an object, you can wrap it in a Transfer() call - this call and how it works should be opaque.

function mySystem(thread: Thread<T>) {
  thread.myFunc(Thread.transfer(someTransferableObject));
}

For implementation, this could either be a wrapper that must be unwrapped by the proxy handler, or simply push a value into a local array.

Option 2 (Transferable Argument)

Add a final argument that accepts transferable objects.

function mySystem(thread: Thread<T>) {
  thread.myFunc(someTransferableObject, [someTransferableObject]);
}

Not sure how to differentiate between normal arguments and transfers in this case, though

[CLEANUP] Investigate using structs for Commands

Commands are currently working very directly with pointers & pointer data. This has already led to one difficult-to-track-down bug where component ids were being set as u16s but read as u32s. Using structs for commands and yielding those when iterating would make this would prevent differences in read/write types, and just be a lot cleaner overall.

Some additional thoughts:

  • This could make it pretty easy to create a consumer-facing custom commands API in the future.
  • Currently the same command can have different sizes, which is not permitted for structs.
    • Could require these to Box their contents, but this makes command data non-contiguous and requires a fair amount of additional work for the allocator
    • Allow command size and struct size to mismatch. Kind of odd, but it might make sense in this case?

[FEAT] Query iterator inlining

Describe the problem this feature solves

Inlining query iterators would be really cool and likely a solid performance jump in effectively every environment. Custom for...of iterators are typically kind of slow

Describe the solution you'd like to see

Handled by the transformer. Because query iteration is inherently nested this will require some sort of context tracking and labels to preserve breaks and continues

[FEAT] Expose struct field decorator API

It's currently not really possible to create custom struct field decorators. The current internal API isn't too bad, but should probably be cleaned up / rethought before being exposed.

Blocked by #3

[FEAT] Query iterator methods

Describe the problem this feature solves

Definitely want to introduce a reduce() method for queries.
Maybe we just do map()and forEach() while we're at it? Writing the iteration is less boiler-platey these days

[FEAT] Don't enqueue commands for despawned entities

Currently we enqueue all commands, even if they're for entities we (could) know are despawned. To save on space, we should ignore these commands.

Technically this could remove the conditional check for an entity being alive before moving tables, but we'll likely introduce immediate Entity creation/deletion methods on World, so this should probably remain in place.

[FEAT] Investigate splitting archetypes and tables

Describe the problem this feature solves

We could make it so that archetype changes aren't necessarily table moves when only ZSTs change.
Requires archetype queries to hold a list of entities they match.

This is hypothetically fast and is what Flecs does, but who knows what perf looks like in JS. Need to make a real example and benchmark

[BUG] Incorrect console warn

Bug Description

Ordering of World.p.prepare() is wrong and so async plugins can cause the no applyCommands warning to appear - oops

[FEAT] Allow structs to be unconditionally extended

Currently, you can't add fields when extending a struct - this restriction should be removed.

This should technically be possible as long as the added fields are ordered after the inherited fields. This means in some circumstances, we may have to upscale the child class's perception of the parent class's alignment/size.

I think it would be good to introduce this possibility to remove the usage caveat and make it easier to get started with Thyseus, and add a docs section on optimizing memory usage & performance that mentions that this pattern is potentially wasteful, and could be a place for consumers of the library to optimize, if needed.

[BUG] @struct.string does not work in multithreaded environments

Bug Description
Can't use strings in multithreaded environments as TextEncoder/TextDecoder are forbidden from operating on SABs (at least in some browsers).

Thyseus Version (e.g. v0.9.0)
0.12.0

Bundler / Build Tool (e.g. Vite, Webpack)
All

Environment & Version (e.g. <browser> <version>, Node <version>)
All, multithreaded

[FEAT] `Query.p.get()`

Describe the problem this feature solves

Getting components from a query for a specific entity should be possible. This is especially useful when dealing with multiple queries:

export function moveCar(
    cars: Query<Car>,
    carPhysics: Query<[Entity, Transform], With<CarPhysics>>,
    carVisual: Query<[Entity, Transform], With<CarVisuals>>
) {
    for (const car of cars) {
        for (const [physicsEntity, physicsTransform] of carPhysics) {
            if (car.physicsId === entity.id) {
                for (const [visualEntity, visualTransform] of carVisual) {
                    if (car.visualId == visualEntity.id) {
                      // holy nesting!
                    }
                }
            }
        }
    }
}

could instead be

export function moveCar(
    cars: Query<Car>,
    carPhysics: Query<[Entity, Transform], With<CarPhysics>>,
    carVisual: Query<[Entity, Transform], With<CarVisuals>>
) {
    for (const car of cars) {
      const [physicsEntity, physicsTransform] = carPhysics.get(car.physicsId);
      const [visualEntity, visualTransform] = carVisual.get(car.visualId);
      // Much nicer!
    }
}

The question of what happens when you call .get() with an entity that isn't in the query needs answering.

Queries will also need to know which group in the vector corresponds to which table.

[FEAT] Run conditions

Describe the problem this feature solves

A mechanism to specify whether a system or group of systems should run at all would be nice. You can kind of do this with an early return, but you may have to access data you otherwise don't care about to do this, which isn't great. Also maybe better optimization potential if they exist external to systems.

Describe the solution you'd like to see

For the API, a run condition is a function that returns a boolean - true if the system(s) should run, false if they should not. It will be supplied as the third argument for World.p.addSystems (Schedule addSystem unknown).
The function can either be a system, or a function that accepts the world instance. If the getSystemParameters property exists on the function, it will be treated as a system and its arguments will be parsed. If not, it will be supplied the world.

Example usage:

type RunCondition = (...args: any[]) => boolean;
class World {
  addSystems(
    scheduleType: ScheduleType,
    systems: System | System[],
    condition: RunCondition
  ): this
}

// In use
const world = await new World()
  .addSystems(Schedule, mySystem, world => world.getResource(SomeResource).someProperty)
  .prepare()

Open Questions

  1. When should this be evaluated?
    a. Every runSchedule() call
    b. When prepare() is called
    c. A third API, included in prepare() but called separately?

[FEAT] Investigate struct serialization

Currently, structs have a byte offset (__$$b) and their properties are getters/setters that write directly to this offset. This is relatively performant, but has some disadvantages:

  • Structs must have a byte offset to be used at all - every instantiation is also a (Thyseus) Memory allocation.
  • dropStruct must be used to clean up instances no longer in use.
  • Extending structs forbids adding new fields. This could be worked around but would require a significant rewrite.
  • Common JS types cannot be used in structs. Strings mostly work, but complex types are simply not an option.

All of this adds up to a workable but somewhat sub-optimal developer experience. Structs behave like normal Javascript objects/classes in some ways but not in others, which adds to the complexity of learning and using Thyseus.

Structs could be rewritten to actually be plain Javascript objects that must define serialize() and deserialize() methods, which would carry the responsibility of defining how objects write their data to Memory. This would essentially remove all of the above disadvantages, and allow developers to think of structs as normal Javascript classes with essentially no caveats. It would also open the door to an API that lets the transformer do the heavy lifting of making classes into structs.

Investigate the performance implications of this change.

[CLEANUP] Rewrite system dependency parsing

System dependencies should probably be handled by constructing a dependency graph. This rewrite could include rewriting executors to accept a graph as well, or just using the graph internally.

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.