Socket
Book a DemoInstallSign in
Socket

@arancini/react

Package Overview
Dependencies
Maintainers
1
Versions
46
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@arancini/react - npm Package Compare versions

Comparing version

to
1.0.0

8

dist/index.d.ts

@@ -30,3 +30,3 @@ import * as A from '@arancini/core';

export declare type QueryEntitiesProps = {
query: A.QueryDescription;
query: A.Query | A.QueryDescription;
children: ReactNode | ((entity: A.Entity) => ReactNode);

@@ -43,9 +43,9 @@ };

Entities: ({ entities, children }: EntitiesProps) => JSX.Element;
QueryEntities: ({ query: queryDescription, children, }: QueryEntitiesProps) => JSX.Element;
QueryEntities: ({ query, children }: QueryEntitiesProps) => JSX.Element;
Component: <T extends A.Component>({ args, children, type, }: ComponentProps<T>) => React.ReactElement<any, string | React.JSXElementConstructor<any>> | null;
System: <T_1 extends A.System>({ type, priority }: SystemProps<T_1>) => null;
useQuery: (queryDescription: A.QueryDescription) => A.Query;
useQuery: (q: A.Query | A.QueryDescription) => A.Query;
useCurrentEntity: () => EntityProviderContext;
useCurrentSpace: () => A.Space;
step: (delta: number) => void;
update: (delta: number) => void;
world: A.World;

@@ -52,0 +52,0 @@ spaceContext: React.Context<SpaceProviderContext>;

@@ -54,6 +54,6 @@ import * as A from "@arancini/core";

const world = existing != null ? existing : new A.World();
if (!existing) {
if (!world.initialised) {
world.init();
}
const step = (delta) => {
const update = (delta) => {
world.update(delta);

@@ -70,3 +70,3 @@ };

}) => {
const [space, setSpace] = useState(null);
const [context, setContext] = useState(null);
useIsomorphicLayoutEffect(() => {

@@ -76,5 +76,7 @@ const newSpace = world.create.space({

});
setSpace(newSpace);
setContext({
space: newSpace
});
return () => {
setSpace(null);
setContext(null);
newSpace.destroy();

@@ -84,5 +86,3 @@ };

return /* @__PURE__ */ jsx(spaceContext.Provider, {
value: {
space
},
value: context,
children

@@ -96,16 +96,23 @@ });

const space = useCurrentSpace();
const [entity, setEntity] = useState(null);
const [context, setContext] = useState(null);
useIsomorphicLayoutEffect(() => {
if (existingEntity) {
setEntity(existingEntity);
if (!space) {
return;
}
if (!space) {
if (existingEntity) {
setContext({
entity: existingEntity
});
return;
}
const newEntity = space.create.entity();
setEntity(newEntity);
setContext({
entity: newEntity
});
const {
id
} = newEntity;
return () => {
setEntity(null);
if (newEntity.alive) {
setContext(null);
if (id === newEntity.id) {
newEntity.destroy();

@@ -116,5 +123,3 @@ }

return /* @__PURE__ */ jsx(entityContext.Provider, {
value: {
entity
},
value: context,
children

@@ -133,6 +138,9 @@ });

});
const useQuery = (queryDescription) => {
const useQuery = (q2) => {
const query = useMemo(() => {
return world.create.query(queryDescription);
}, [queryDescription]);
if (q2 instanceof A.Query) {
return q2;
}
return world.create.query(q2);
}, [q2]);
const rerender = useRerender();

@@ -151,8 +159,10 @@ useIsomorphicLayoutEffect(() => {

const QueryEntities = ({
query: queryDescription,
query,
children
}) => {
const query = useQuery(queryDescription);
const {
entities
} = useQuery(query);
return /* @__PURE__ */ jsx(Entities, {
entities: query.entities,
entities,
children

@@ -167,9 +177,10 @@ });

const ref = useRef(null);
const {
entity
} = useContext(entityContext);
const entityCtx = useContext(entityContext);
useIsomorphicLayoutEffect(() => {
if (!entity || !entity.alive) {
if (!entityCtx || !entityCtx.entity.space) {
return;
}
const {
entity
} = entityCtx;
let newComponent;

@@ -182,7 +193,7 @@ if (children) {

return () => {
if (entity.has(newComponent.__internal.class)) {
if (entity.find(newComponent._class) === newComponent) {
entity.remove(newComponent);
}
};
}, [entity, args, children, type]);
}, [entityCtx, args, children, type]);
if (children) {

@@ -220,3 +231,3 @@ const child = React.Children.only(children);

useCurrentSpace,
step,
update,
world,

@@ -223,0 +234,0 @@ spaceContext,

@@ -1,2 +0,2 @@

(function(l,y){typeof exports=="object"&&typeof module!="undefined"?y(exports,require("@arancini/core"),require("react")):typeof define=="function"&&define.amd?define(["exports","@arancini/core","react"],y):(l=typeof globalThis!="undefined"?globalThis:l||self,y(l.index={},l.A,l.React))})(this,function(l,y,c){"use strict";function w(e){return e&&typeof e=="object"&&"default"in e?e:{default:e}}function h(e){if(e&&e.__esModule)return e;var r=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});return e&&Object.keys(e).forEach(function(s){if(s!=="default"){var u=Object.getOwnPropertyDescriptor(e,s);Object.defineProperty(r,s,u.get?u:{enumerable:!0,get:function(){return e[s]}})}}),r.default=e,Object.freeze(r)}var P=h(y),S=w(c);const d=typeof window!="undefined"?c.useLayoutEffect:c.useEffect,T=e=>r=>{e.forEach(s=>{typeof s=="function"?s(r):s&&(s.current=r)})};function q(){const[e,r]=c.useState(0);return c.useCallback(()=>{r(s=>s+1)},[])}var C={exports:{}},E={};/**
(function(l,m){typeof exports=="object"&&typeof module!="undefined"?m(exports,require("@arancini/core"),require("react")):typeof define=="function"&&define.amd?define(["exports","@arancini/core","react"],m):(l=typeof globalThis!="undefined"?globalThis:l||self,m(l.index={},l.A,l.React))})(this,function(l,m,c){"use strict";function h(t){return t&&typeof t=="object"&&"default"in t?t:{default:t}}function T(t){if(t&&t.__esModule)return t;var r=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});return t&&Object.keys(t).forEach(function(s){if(s!=="default"){var o=Object.getOwnPropertyDescriptor(t,s);Object.defineProperty(r,s,o.get?o:{enumerable:!0,get:function(){return t[s]}})}}),r.default=t,Object.freeze(r)}var O=T(m),x=h(c);const p=typeof window!="undefined"?c.useLayoutEffect:c.useEffect,L=t=>r=>{t.forEach(s=>{typeof s=="function"?s(r):s&&(s.current=r)})};function A(){const[t,r]=c.useState(0);return c.useCallback(()=>{r(s=>s+1)},[])}var C={exports:{}},S={};/**
* @license React

@@ -9,2 +9,2 @@ * react-jsx-runtime.production.min.js

* LICENSE file in the root directory of this source tree.
*/var L=S.default,A=Symbol.for("react.element"),I=Symbol.for("react.fragment"),M=Object.prototype.hasOwnProperty,k=L.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,F={key:!0,ref:!0,__self:!0,__source:!0};function x(e,r,s){var u,p={},_=null,v=null;s!==void 0&&(_=""+s),r.key!==void 0&&(_=""+r.key),r.ref!==void 0&&(v=r.ref);for(u in r)M.call(r,u)&&!F.hasOwnProperty(u)&&(p[u]=r[u]);if(e&&e.defaultProps)for(u in r=e.defaultProps,r)p[u]===void 0&&(p[u]=r[u]);return{$$typeof:A,type:e,key:_,ref:v,props:p,_owner:k.current}}E.Fragment=I,E.jsx=x,E.jsxs=x,C.exports=E;const m=C.exports.jsx,N=C.exports.Fragment,D=e=>{const r=c.createContext(null),s=c.createContext(null),u=e!=null?e:new P.World;e||u.init();const p=t=>{u.update(t)},_=()=>c.useContext(s),v=()=>{const t=c.useContext(r);return t?t.space:u.defaultSpace},Q=({id:t,children:n})=>{const[o,a]=c.useState(null);return d(()=>{const i=u.create.space({id:t});return a(i),()=>{a(null),i.destroy()}},[t]),m(r.Provider,{value:{space:o},children:n})},U=({children:t,entity:n})=>{const o=v(),[a,i]=c.useState(null);return d(()=>{if(n){i(n);return}if(!o)return;const f=o.create.entity();return i(f),()=>{i(null),f.alive&&f.destroy()}},[o]),m(s.Provider,{value:{entity:a},children:t})},O=c.memo(U),b=({entities:t,children:n})=>m(N,{children:t.map(o=>m(O,{entity:o,children:typeof n=="function"?n(o):n},o.id))}),j=t=>{const n=c.useMemo(()=>u.create.query(t),[t]),o=q();return d(()=>(n.onEntityAdded.add(o),n.onEntityRemoved.add(o),()=>{n.onEntityAdded.remove(o),n.onEntityRemoved.remove(o)}),[o]),d(o,[]),n};return{Space:Q,Entity:O,Entities:b,QueryEntities:({query:t,children:n})=>{const o=j(t);return m(b,{entities:o.entities,children:n})},Component:({args:t,children:n,type:o})=>{const a=c.useRef(null),{entity:i}=c.useContext(s);if(d(()=>{if(!i||!i.alive)return;let f;return n?f=i.add(o,a.current):f=i.add(o,...t!=null?t:[]),()=>{i.has(f.__internal.class)&&i.remove(f)}},[i,t,n,o]),n){const f=S.default.Children.only(n);return S.default.cloneElement(f,{ref:T([f.ref,a])})}return null},System:({type:t,priority:n})=>(d(()=>(u.registerSystem(t,{priority:n}),()=>{u.unregisterSystem(t)}),[t,n]),null),useQuery:j,useCurrentEntity:_,useCurrentSpace:v,step:p,world:u,spaceContext:r,entityContext:s}};l.createECS=D,Object.defineProperties(l,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
*/var I=x.default,M=Symbol.for("react.element"),k=Symbol.for("react.fragment"),D=Object.prototype.hasOwnProperty,F=I.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,N={key:!0,ref:!0,__self:!0,__source:!0};function b(t,r,s){var o,y={},E=null,v=null;s!==void 0&&(E=""+s),r.key!==void 0&&(E=""+r.key),r.ref!==void 0&&(v=r.ref);for(o in r)D.call(r,o)&&!N.hasOwnProperty(o)&&(y[o]=r[o]);if(t&&t.defaultProps)for(o in r=t.defaultProps,r)y[o]===void 0&&(y[o]=r[o]);return{$$typeof:M,type:t,key:E,ref:v,props:y,_owner:F.current}}S.Fragment=k,S.jsx=b,S.jsxs=b,C.exports=S;const _=C.exports.jsx,Q=C.exports.Fragment,g=t=>{const r=c.createContext(null),s=c.createContext(null),o=t!=null?t:new O.World;o.initialised||o.init();const y=e=>{o.update(e)},E=()=>c.useContext(s),v=()=>{const e=c.useContext(r);return e?e.space:o.defaultSpace},q=({id:e,children:n})=>{const[u,d]=c.useState(null);return p(()=>{const f=o.create.space({id:e});return d({space:f}),()=>{d(null),f.destroy()}},[e]),_(r.Provider,{value:u,children:n})},U=({children:e,entity:n})=>{const u=v(),[d,f]=c.useState(null);return p(()=>{if(!u)return;if(n){f({entity:n});return}const i=u.create.entity();f({entity:i});const{id:a}=i;return()=>{f(null),a===i.id&&i.destroy()}},[u]),_(s.Provider,{value:d,children:e})},j=c.memo(U),w=({entities:e,children:n})=>_(Q,{children:e.map(u=>_(j,{entity:u,children:typeof n=="function"?n(u):n},u.id))}),P=e=>{const n=c.useMemo(()=>e instanceof O.Query?e:o.create.query(e),[e]),u=A();return p(()=>(n.onEntityAdded.add(u),n.onEntityRemoved.add(u),()=>{n.onEntityAdded.remove(u),n.onEntityRemoved.remove(u)}),[u]),p(u,[]),n};return{Space:q,Entity:j,Entities:w,QueryEntities:({query:e,children:n})=>{const{entities:u}=P(e);return _(w,{entities:u,children:n})},Component:({args:e,children:n,type:u})=>{const d=c.useRef(null),f=c.useContext(s);if(p(()=>{if(!f||!f.entity.space)return;const{entity:i}=f;let a;return n?a=i.add(u,d.current):a=i.add(u,...e!=null?e:[]),()=>{i.find(a._class)===a&&i.remove(a)}},[f,e,n,u]),n){const i=x.default.Children.only(n);return x.default.cloneElement(i,{ref:L([i.ref,d])})}return null},System:({type:e,priority:n})=>(p(()=>(o.registerSystem(e,{priority:n}),()=>{o.unregisterSystem(e)}),[e,n]),null),useQuery:P,useCurrentEntity:E,useCurrentSpace:v,update:y,world:o,spaceContext:r,entityContext:s}};l.createECS=g,Object.defineProperties(l,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
{
"name": "@arancini/react",
"description": "React glue for the 'arancini' object based Entity Component System",
"keywords": [
"react",
"gamedev",
"ecs",
"entity-component-system"
],
"type": "module",

@@ -7,3 +14,3 @@ "packageManager": "yarn@3.2.1",

"license": "MIT",
"version": "0.1.1",
"version": "1.0.0",
"scripts": {

@@ -15,4 +22,14 @@ "dev": "vite",

"build-storybook": "build-storybook",
"test": "jest"
"test": "jest --coverage"
},
"dependencies": {
"@arancini/core": "^1.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"peerDependencies": {
"@arancini/core": "^1.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {

@@ -59,13 +76,3 @@ "@babel/core": "^7.18.6",

}
},
"dependencies": {
"@arancini/core": "0.1.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"peerDependencies": {
"arancini": "0.1.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
}

@@ -13,3 +13,3 @@ # @arancini/react

To get started, use `createECS` to get React glue scoped to a arancini world. Because components and hooks are scoped, libraries can use them without worrying about context conflicts.
To get started, use `createECS` to get glue components and hooks scoped to a given arancini world. Because the react glue is scoped, libraries can use @arancini/react without worrying about context conflicts.

@@ -25,4 +25,4 @@ ```ts

```ts
import { createECS } from '@arancini/react'
import { World } from 'arancini'
import { createECS } from '@arancini/react'

@@ -35,3 +35,3 @@ const world = new World()

The `createECS` function returns a reference to the arancini world. If you didn't pass in an existing world, you can still use the regular imperative API directly.
`createECS` returns a reference to the arancini world, so if you didn't pass `createECS` in an existing world, you can still use the regular imperative API.

@@ -42,3 +42,8 @@ ```ts

// use the World as normal
/* use the world as normal */
// register components
world.registerComponent(MyComponent)
// create entities
const entity = world.create.entity()

@@ -49,3 +54,3 @@ ```

`@arancini/react` does not automatically step the World for you, the `step` method must be called. If you are using arancini with `@react-three/fiber`, you can use the `useFrame` hook to step the World.
`@arancini/react` does not automatically step the world for you. If you are using arancini with `@react-three/fiber`, you can use the `useFrame` hook to step the World.

@@ -57,3 +62,3 @@ ```tsx

useFrame((_, delta) => {
ECS.step(delta)
ECS.update(delta)
})

@@ -65,6 +70,25 @@

### Creating Entities and Components
If arancini needs to be integrated into an existing game loop, instead of calling `step`, you can decide when to update parts of the world.
The `Entity` can be used to declaratively create entities, and `Component` can be used to add components to an entity.
```tsx
const ECS = createECS()
const Example = () => {
useFrame(({ clock: { elapsedTime }, delta) => {
// update all systems
this.systemManager.update(delta, elapsedTime)
// or update a particular system
const exampleSystem = ECS.world.getSystem(ExampleSystem)
exampleSystem.update(delta, elapsedTime)
})
}
```
### Spaces, Entities, Components
`<Space />` can be used to declaratively create spaces, `<Entity />` can be used to declaratively create entities, and `<Component />` can be used to add components to an entity.
`<Component />` will automatically register the component with the world if it hasn't been registered yet.
```tsx

@@ -82,9 +106,11 @@ class Position extends A.Component {

const Example = () => (
<ECS.Entity>
<ECS.Component type={Position} args={[0, 0]} />
</ECS.Entity>
<ECS.Space>
<ECS.Entity>
<ECS.Component type={Position} args={[0, 0]} />
</ECS.Entity>
</ECS.Space>
)
```
You can also pass an existing entity to `Entity`.
You can also pass an existing entity to `<Entity />`.

@@ -102,2 +128,26 @@ ```tsx

`@arancini/react` also provides an `<Entities />` component that can be used to render a list of entities or add components to existing entities. `<Entities />` also supports [render props](https://reactjs.org/docs/render-props.html).
```tsx
const SimpleExample = () => (
<ECS.Entities entities={[entity1, entity2]}>
{/* ... */}
</ECS.Entities>
)
const AddComponentToEntities = () => (
<ECS.Entities entities={[entity1, entity2]}>
<ECS.Component type={Position} args={[0, 0]} />
</ECS.Entities>
)
const RenderProps = () => (
<ECS.Entities entities={[entity1, entity2]}>
{(entity) => {
// ...
}}
</ECS.Entities>
)
```
### Querying the world

@@ -107,3 +157,3 @@

The `useQuery` hook queries the world for entities with given components, and will re-render when the query results change.
The `useQuery` hook queries the world for entities with given components and will re-render when the query results change.

@@ -173,4 +223,2 @@ ```tsx

)
const
```

@@ -205,6 +253,38 @@

## Advanced
### Systems
### Using Entity and Space contexts
`@arancini/react` provides a `<System />` helper component that can be used to register a system. It will automatically unregister the system when the component unmounts.
```tsx
import * as A from 'arancini'
class ExampleSystem extends A.System {
update(delta: number) {
// ...
}
}
const Example = () => (
<ECS.System type={ExampleSystem} priority={10} />
)
```
You can also opt to register systems using the imperative API.
```tsx
const Example = () => {
useEffect(() => {
ECS.world.registerSystem(ExampleSystem, { priority: 10 })
return () => {
ECS.world.unregisterSystem(ExampleSystem)
}
}, [])
}
```
## Advanced Usage
### Entity and Space contexts
You can use the hooks `useCurrentEntitiy` and `useCurrentSpace` to access the current entity and space in a React component.

@@ -244,2 +324,2 @@

For truly advanced usage, `createECS` also returns the contexts themselves, `entityContext` and `spaceContext`.
For truly advanced usage, `createECS` also returns the react contexts, `entityContext` and `spaceContext`.
SocketSocket SOC 2 Logo

Product

About

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.

  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc

U.S. Patent No. 12,346,443 & 12,314,394. Other pending.