npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@gwenjs/core

v0.2.2

Published

GWEN Core Engine - ECS + WASM

Downloads

62

Readme

@gwenjs/core

Core primitives for the GWEN game engine — entity management, components, systems, actors, scenes, and the plugin architecture.

Installation

npm install @gwenjs/core

Subpath imports

@gwenjs/core exposes a root entrypoint and three additional subpath entrypoints to keep bundle sizes small:

| Subpath | What it exports | | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | @gwenjs/core | createEngine, useEngine, defineComponent, Types, createLogger, initWasm, onCleanup | | @gwenjs/core/system | defineSystem, onUpdate, onBeforeUpdate, onAfterUpdate, onRender, useQuery, useService, useWasmModule | | @gwenjs/core/actor | defineActor, onStart, onDestroy, onEvent, useEntityId, definePrefab, defineEvents, emit, useActor, useComponent, usePrefab, useTransform, defineLayout, useLayout, placeActor, placeGroup, placePrefab | | @gwenjs/core/scene | defineScene, defineSceneRouter, useSceneRouter |

Quick start

import { createEngine, defineComponent, Types } from "@gwenjs/core";
import { defineSystem, useQuery, onUpdate } from "@gwenjs/core/system";

// 1. Define components (Structure-of-Arrays)
const Position = defineComponent({
  name: "Position",
  schema: { x: Types.f32, y: Types.f32 },
});

const Velocity = defineComponent({
  name: "Velocity",
  schema: { x: Types.f32, y: Types.f32 },
});

// 2. Define a system
const MovementSystem = defineSystem("MovementSystem", () => {
  const entities = useQuery([Position, Velocity]);

  onUpdate((dt) => {
    for (const entity of entities) {
      const pos = entity.get(Position)!;
      const vel = entity.get(Velocity)!;
      pos.x += vel.x * dt;
      pos.y += vel.y * dt;
    }
  });
});

// 3. Create and start the engine
const engine = await createEngine();
await engine.use(MovementSystem);
engine.start();

Components

Components describe the data layout of entities. Fields are declared with Types.* and component data is stored and accessed via the engine.

import { defineComponent, Types, useEngine } from "@gwenjs/core";

const Health = defineComponent({
  name: "Health",
  schema: { hp: Types.f32, max: Types.f32 },
});

// Add component data to an entity
const engine = useEngine();
engine.addComponent(entityId, Health, { hp: 100, max: 100 });

// Read in a system via entity accessor
for (const entity of entities) {
  const health = entity.get(Health);
  if (health && health.hp <= 0) {
    /* ... */
  }
}

Available types: Types.f32, Types.f64, Types.i32, Types.i64, Types.u32, Types.u64, Types.bool, Types.string, Types.persistentString, Types.vec2, Types.vec3, Types.vec4, Types.quat, Types.color.

Systems

Systems run game logic every frame. Use useQuery to iterate entities and frame hooks to schedule work.

import {
  defineSystem,
  useQuery,
  onUpdate,
  onBeforeUpdate,
  onAfterUpdate,
  onRender,
} from "@gwenjs/core/system";

const PhysicsSystem = defineSystem("PhysicsSystem", () => {
  const entities = useQuery([Position, Velocity]);

  onBeforeUpdate((dt) => {
    /* runs before update */
  });
  onUpdate((dt) => {
    /* main update */
  });
  onAfterUpdate((dt) => {
    /* runs after update */
  });
  onRender(() => {
    /* render pass */
  });
});

useQuery must be called during the setup phase, not inside a frame hook.

Actors

Actors are entity-bound objects with a lifecycle. Use defineActor for interactive game objects.

import {
  defineActor,
  onStart,
  onDestroy,
  onEvent,
  definePrefab,
  defineEvents,
  useComponent,
} from "@gwenjs/core/actor";

const EnemyPrefab = definePrefab([
  { def: Position, defaults: { x: 0, y: 0 } },
  { def: Health, defaults: { hp: 100, max: 100 } },
]);

const EnemyEvents = defineEvents({
  "enemy:hit": (damage: number) => {},
});

const EnemyActor = defineActor(EnemyPrefab, (props: { hp: number }) => {
  const health = useComponent<{ hp: number; max: number }>(Health);

  onStart(() => {
    health.hp = props.hp;
    health.max = props.hp;
  });

  onDestroy(() => {
    console.log("Enemy destroyed");
  });

  return {
    takeDamage: (n: number) => {
      health.hp -= n;
    },
  };
});

// Register and spawn
await engine.use(EnemyActor._plugin);
const id = EnemyActor._plugin.spawn({ hp: 50 });
EnemyActor._plugin.despawn(id);

Lifecycle hooks (actor only)

| Hook | When | | -------------------- | ------------------------------------------- | | onStart(fn) | Once, after spawn | | onDestroy(fn) | Once, on despawn | | onEvent(event, fn) | On event — automatically removed on despawn |

useEntityId

Returns the entity ID of the actor being set up. Valid only during the defineActor factory function (including composables called from it).

const MyActor = defineActor(MyPrefab, () => {
  const id = useEntityId(); // bigint
});

Events

import { defineEvents, emit, onEvent, type InferEvents } from '@gwenjs/core/actor'
import { useEngine } from '@gwenjs/core'

// Declare the event contract
export const GameEvents = defineEvents({
  'player:died': () => {},
  'enemy:hit': (damage: number) => {},
})

// Augment GwenRuntimeHooks to get full type-checking on emit/onEvent/hook
declare module '@gwenjs/core' {
  interface GwenRuntimeHooks extends InferEvents<typeof GameEvents> {}
}

// Emit from inside an engine context (system, actor, or engine lifecycle callback)
emit('enemy:hit', 42)

// Listen inside an actor (auto-cleaned up on despawn)
onEvent('enemy:hit', (damage) => { ... })

// Listen from a system
const engine = useEngine()
engine.hooks.hook('enemy:hit', (damage) => { ... })

Scenes

import { defineScene, defineSceneRouter, useSceneRouter } from '@gwenjs/core/scene'

const MenuScene = defineScene({
  name: 'menu',
  systems: [MenuSystem],
})

const GameScene = defineScene({
  name: 'game',
  systems: [MovementSystem, PhysicsSystem],
})

const AppRouter = defineSceneRouter({
  initial: 'menu',
  routes: {
    menu: { scene: MenuScene, on: { START: 'game' } },
    game: { scene: GameScene, on: { PAUSE: 'menu' } },
  },
})

// Navigate — useSceneRouter must be called inside an engine context
const PlayerActor = defineActor(PlayerPrefab, () => {
  const nav = useSceneRouter(AppRouter)
  onUpdate(() => {
    if (/* game over */) nav.send('PAUSE')
  })
  return {}
})

// Or inside a system
const NavSystem = defineSystem('NavSystem', () => {
  const nav = useSceneRouter(AppRouter)
  onUpdate(() => {
    console.log(nav.current)
  })
})

Plugin system

import { createEngine } from "@gwenjs/core";

const engine = await createEngine();

// Plugins are GwenPlugin objects returned by defineSystem, defineActor._plugin, etc.
await engine.use(MovementSystem);
await engine.use(EnemyActor._plugin);

engine.start();
engine.stop();

Logger

import { createLogger } from "@gwenjs/core";

const log = createLogger("game:my-system");
log.info("hello");
log.warn("something odd");
log.error("oops");

License

MIT