@javelin/devtool
Version:
Developer tool for Javelin ECS.
182 lines (165 loc) • 11.8 kB
JavaScript
;function e(e){return e&&"object"==typeof e&&"default"in e?e.default:e}Object.defineProperty(exports,"__esModule",{value:!0});var t,n=require("@javelin/net"),l=require("react"),o=e(l),r=require("react-dom"),a=require("react-router-dom"),c=require("styled-components"),s=e(c),i=e(require("immer")),m=require("@javelin/ecs");function d(e,n){return i(e,e=>{switch(n.type){case t.Info:{const t={...n.payload},l=e.messages[e.messages.length-1];if(!l||!l.context||t.context&&t.context.id===l.context.id||(l.start=-1/0),t.context){const n=e.messages.findIndex(e=>e.context&&e.context.id===t.context.id&&t.time-(e.start||0)<=t.context.duration);if(n>-1){const l=e.messages[n];return t.count=(l.count||0)+1,t.start=l.start,void(e.messages[n]=t)}t.count=0,t.start=t.time}e.messages.push(t),e.messages=e.messages.slice(e.messages.length+1-e.limit)}}})}!function(e){e.Info="log/info"}(t||(t={}));const u={limit:0,messages:[]};const p=l.createContext({messages:[],limit:0,info(){}}),f=l.forwardRef((function(e,n){const r=function(e=100){const n=l.useRef(0),[o,r]=l.useReducer(d,{...u,limit:e});return{info(e,l){r({type:t.Info,payload:{id:++n.current,time:+performance.now().toFixed(2),text:e,context:l}})},...o}}(e.limit);return l.useImperativeHandle(n,()=>r),o.createElement(p.Provider,{value:r},e.children)}));function y(){return l.useContext(p)}const g=l.createContext({worlds:[],sendMessage:()=>{}}),E=()=>l.useContext(g);function h(e){const t={worlds:e.worlds,sendMessage:(t,n)=>e.onMessage(t,n)};return o.createElement(g.Provider,{value:t},e.children)}function x(e,t){var n;return null===(n=t.find(t=>e===t.type))||void 0===n?void 0:n.name}const w=s.div`
display: flex;
`,v=s.div`
display: flex;
flex: 1;
flex-direction: column;
justify-content: center;
align-items: center;
`,b=s.button`
display: flex;
`,k=s.select`
flex: 2;
`,C=s.ul`
list-style-type: none;
margin: 0 0 8px 0;
padding: 0;
`,S=s.ul`
list-style-type: none;
margin: 0;
padding: 0;
`,M=s.li`
margin: 8px 0;
`;function z(e,t){return i(e,e=>{switch(t.type){case"add_components":e.components.push(...t.payload);break;case"remove_components":e.components=e.components.filter(e=>!t.payload.includes(e.type));break;case"set":{const n=e.components.findIndex(e=>e.type===t.payload.type);e.components[n][t.payload.key]=t.payload.value;break}case"reset":m.mutableEmpty(e.components)}})}function I(e,t){return"number"===e?Number(t):t}function P(){const{world:e}=a.useParams(),{worlds:t,sendMessage:r}=E(),c=t.find(t=>t.name===e),[s,i]=l.useReducer(z,{components:[]}),d=y(),u=l.useCallback(()=>{r(c,n.protocol.spawn(s.components.map(e=>({...e})))),g([]),P([]),d.info("Spawned "+JSON.stringify(s.components)),i({type:"reset",payload:c})},[s,c]);function p(e){const{schema:t}=c.model.find(t=>t.type===e);return Object.entries(t).reduce((e,[t,n])=>(e[t]="number"===n?m.number.create():m.string.create(),e),{type:e})}l.useEffect(()=>{i({type:"reset",payload:c})},[c]);const[f,g]=l.useState([]),[h,P]=l.useState([]);function R(e,t){const n=c.model.find(t=>t.type===e.type);for(const e in n.schema)if(t===e)return n.schema[e];throw new Error(`Field ${t} does not exist on component with type ${x(e.type,c.model)}`)}return o.createElement("div",null,o.createElement("h4",null,"Spawn"),o.createElement("p",null,"Create new entities by moving component types into the box on the right side."),o.createElement(w,null,o.createElement(k,{multiple:!0,value:f.map(String),onChange:function(e){const t=Array.from(e.target.selectedOptions).map(e=>Number(e.value));g(t)}},c.model.filter(e=>void 0===s.components.find(t=>t.type===e.type)).map(e=>o.createElement("option",{key:e.type,value:e.type},e.name))),o.createElement(v,null,o.createElement(b,{onClick:function(){i({type:"add_components",payload:f.map(p)}),g([]),P([...h,...f])}},">"),o.createElement(b,{onClick:function(){i({type:"remove_components",payload:h}),P([])}},"<")),o.createElement(k,{multiple:!0,value:h.map(String),onChange:function(e){const t=Array.from(e.target.selectedOptions).map(e=>Number(e.value));P(t)}},s.components.map(e=>o.createElement("option",{key:e.type,value:e.type},x(e.type,c.model))))),o.createElement(C,null,h.map(e=>s.components.find(t=>t.type===e)).map(e=>o.createElement("li",{key:e.type},o.createElement("h4",null,x(e.type,c.model)),o.createElement("fieldset",null,o.createElement(S,null,Object.entries(e).filter(([e])=>"type"!==e).map(([t,n])=>{return o.createElement(M,{key:t},o.createElement("label",{htmlFor:t},t),o.createElement("input",{id:t,name:t,type:(l=R(e,t),"number"===l?"number":"text"),value:String(n),onChange:n=>i({type:"set",payload:{type:e.type,key:t,value:I(R(e,t),n.target.value)}})}));var l})))))),o.createElement("button",{onClick:u},"Spawn"))}function R(){const{world:e}=a.useParams(),{worlds:t}=E(),n=t.find(t=>t.name===e).world,l=n[m.$worldStorageKey].archetypes.length,r=n[m.$worldStorageKey].archetypes.reduce((e,t)=>e+t.entities.length,0);return o.createElement("div",null,o.createElement("h4",null,"Details"),o.createElement("dl",null,o.createElement("dt",null,"Archetypes"),o.createElement("dd",null,l),o.createElement("dt",null,"Entities"),o.createElement("dd",null,r)),o.createElement("h4",null,"Actions"),o.createElement("ul",null,o.createElement("li",null,o.createElement(a.Link,{to:`/${e}/spawn`},"Spawn")),o.createElement("li",null,o.createElement(a.Link,{to:`/${e}/inspect`},"Inspect"))))}const $=s.ul`
display: flex;
flex-wrap: wrap;
list-style-type: none;
margin: 0;
padding: 0;
`,j=s.section`
margin-bottom: 1em;
`,L=s.h4`
font-family: "SF Mono", "Consolas", monospace;
font-weight: normal;
text-decoration: underline;
`,_=s.li`
background-color: ${e=>e.selected?"#6688ae":"transparent"};
border-radius: 3px;
border: ${e=>"1px solid "+(e.selected?"#6688ae":"#ccc")};
box-sizing: border-box;
color: ${e=>e.selected?"#fafafa":"inherit"};
cursor: pointer;
flex: 0 0 30px;
margin: 2px;
padding: 4px;
text-align: center;
&:hover {
background-color: ${e=>e.selected?"#6688ae":"#ccc"};
}
`;function O(e,t){return i(e,e=>{switch(t.type){case"select_entity":e.selected.push(t.payload);break;case"deselect_entity":e.selected.splice(e.selected.indexOf(t.payload),1);break;case"reset":m.mutableEmpty(e.selected)}})}function q(){const{world:e}=a.useParams(),{worlds:t,sendMessage:r}=E(),c=t.find(t=>t.name===e),[s,i]=l.useReducer(O,{selected:[]}),d=c.world[m.$worldStorageKey].archetypes.map(e=>{const t=e.entities.slice(0,50);return 0===t.length?null:o.createElement(j,{key:e.layout.join(",")},o.createElement(L,null,e.layout.map(e=>x(e,null==c?void 0:c.model)).join(" & ")),o.createElement("span",null,t.length," of ",e.entities.length),o.createElement($,null,t.map(e=>{const t=s.selected.includes(e);return o.createElement(_,{key:e,selected:t,onClick:()=>i({type:t?"deselect_entity":"select_entity",payload:e})},o.createElement("span",null,e))})))});return o.createElement("div",null,o.createElement("h4",null,"Archetypes"),o.createElement("p",null,"Inspect your game world and modify or delete existing entitites."),d,o.createElement("button",{onClick:()=>{r(c,n.protocol.ops(s.selected.map(e=>[m.WorldOpType.Destroy,e]),!0)),i({type:"reset"})}},"Delete"))}function H(){return o.createElement(a.Switch,null,o.createElement(a.Route,{path:"/:world/inspect",component:q}),o.createElement(a.Route,{path:"/:world/spawn",component:P}),o.createElement(a.Route,{path:"/:world",component:R}))}const A=s.ul`
font-family: "SF Mono", "Consolas", monospace;
font-size: 10px;
list-style-type: none;
margin: 0;
padding: 0;
`;function D(e){const{messages:t}=y();return o.createElement(A,null,t.map(e=>o.createElement("li",{key:e.id},e.time,(e.count||0)>0&&`(${e.count})`,": ",e.text)))}const F=s.div`
display: flex;
flex: 1;
flex-direction: column;
overflow: scroll;
`,N=s.div`
background-color: #fefefe;
padding: 8px;
display: flex;
flex-direction: row;
`,T=s.div`
padding: 8px;
`,K=s.h3`
margin: 0;
`,W=l.forwardRef((e,t)=>o.createElement(F,{ref:t},e.title&&o.createElement(N,null,o.createElement(K,null,e.title)),o.createElement(T,null,e.children))),G=c.createGlobalStyle`
a {
text-decoration: none;
color: #6688ae;
&:hover {
color: #4e677e;
}
}
dl {
display: inline-grid;
grid-template-columns: auto auto;
margin: 0;
dt {
padding: 4px;
text-align: right;
word-break: break-word;
text-decoration: underline;
}
dd {
padding: 4px;
margin: 0;
}
}
fieldset {
border: none;
margin: 0;
padding: 0;
}
select {
> option {
padding: 4px 2px;
}
}
label {
margin-right: 8px;
}
button {
cursor: pointer;
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin: 1em 0;
}
h1 {
font-size: 2em;
}
h2 {
font-size: 1.6em;
}
h3 {
font-size: 1.2em;
}
h4,
h5,
h6 {
font-size: 1em;
}
input,
select,
textarea,
button {
font-size: 12px;
}
`,J=s.div`
background: #efefef;
font-family: Helvetica, sans-serif;
font-size: 12px;
color: #222;
display: flex;
flex-direction: column;
flex: 1;
height: 100%;
overflow-y: auto;
overflow-x: hidden;
*,
*:before,
*:after {
box-sizing: border-box;
}
`,B=(s.div`
padding: 8px;
flex: 1;
overflow: auto;
`,s.select`
margin-right: 8px;
align-self: flex-end;
`);function Q(){const{worlds:e}=E(),{push:t}=a.useHistory(),{pathname:n}=a.useLocation(),r=l.useCallback(n=>t("/"+e[n.target.selectedIndex].name),[]),c=n.match(/^\/(\w+)\/?/),s=c?c[1]:"";return o.createElement(B,{value:s,onChange:r},e.map(e=>o.createElement("option",{key:e.name,value:e.name},e.name)))}const U=s(a.Link)`
&:before {
content: "/";
}
`,V=s.nav`
flex: 1;
`;function X(){const{pathname:e}=a.useLocation(),t=e.replace(/^\//,"").split("/").reduce((e,t)=>(e.currentPath+=t+"/",e.links.push(o.createElement(U,{key:e.currentPath,to:"/"+e.currentPath},t)),e),{currentPath:"",links:[]});return o.createElement(V,null,t.links)}function Y(){const{worlds:e}=E(),t=function(e){const t=l.useRef(null),[n,o]=l.useState(!0),{current:r}=t;return l.useEffect(()=>{if(!r)return;const e=()=>o(r.scrollHeight-r.scrollTop===r.clientHeight);return r.addEventListener("scroll",e),()=>r.removeEventListener("scroll",e)},[r]),l.useEffect(()=>{r&&n&&r.scrollTo({top:r.scrollHeight})},[n,e,r]),t}(y().messages),n="/"+e[0].name;return o.createElement(a.MemoryRouter,{initialEntries:[n]},o.createElement(G,null),o.createElement(J,null,o.createElement(N,null,o.createElement(a.Link,{to:n},"javelin"),o.createElement(X,null),o.createElement(Q,null)),o.createElement(W,null,o.createElement(H,null)),o.createElement(W,{title:"Log",ref:t},o.createElement(D,null))))}exports.createDevtool=function(e){const t=new Map,o=Object.entries(e.worlds).map(([e,t])=>"remote"in t?{name:e,world:t.world,model:n.serializeWorldModel(t.world),remote:!0}:{name:e,world:t,model:n.serializeWorldModel(t),remote:!1});let a,c;for(const{world:e,remote:l}of o)if(!l){const l=n.createMessageHandler();e.addSystem(l.system),t.set(e,l)}return{mount:function(n){const s=l.createRef(),i=(n,l)=>{const{world:r}=o.find(e=>e===n);if(n.remote)e.onMessage(r,l);else{t.get(r).push(l)}},m=()=>r.render(l.createElement(f,{ref:s},l.createElement(h,{worlds:o,onMessage:i},l.createElement(Y,null))),n);return m(),a=n,c=setInterval(m,500),{log:s.current}},unmount:function(){clearInterval(c),r.unmountComponentAtNode(a)},setModel:function(e,t){const n=o.find(t=>t.world===e);if(void 0===n)throw new Error("Cannot set model: world not registered with devtool");n.model=t}}},exports.createDevtoolMessageProducer=function(e){return n.createMessageProducer({components:e.componentTypes.map(e=>({type:e,priority:1})),updateInterval:1e3,updateSize:1e3,isLocal:!0})};