refactor: script
This commit is contained in:
parent
6fdf4e2327
commit
2f7b5d458f
1
TODO.md
1
TODO.md
|
@ -1,3 +1,4 @@
|
||||||
|
- visibleEntities is way overloaded
|
||||||
- entities should probably belong to room instead of layer
|
- entities should probably belong to room instead of layer
|
||||||
- don't tick entities without any ticking traits
|
- don't tick entities without any ticking traits
|
||||||
- ~~production build~~
|
- ~~production build~~
|
||||||
|
|
|
@ -24,7 +24,9 @@
|
||||||
"@avocado/math": "^2.0.0",
|
"@avocado/math": "^2.0.0",
|
||||||
"@avocado/persea": "^1.0.0",
|
"@avocado/persea": "^1.0.0",
|
||||||
"@avocado/resource": "^2.0.0",
|
"@avocado/resource": "^2.0.0",
|
||||||
|
"@avocado/sandbox": "^1.0.0",
|
||||||
"@avocado/traits": "^2.0.0",
|
"@avocado/traits": "^2.0.0",
|
||||||
|
"@babel/parser": "^7.13.13",
|
||||||
"@latus/core": "2.0.0",
|
"@latus/core": "2.0.0",
|
||||||
"autoprefixer": "^9.8.6",
|
"autoprefixer": "^9.8.6",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
|
@ -33,6 +35,7 @@
|
||||||
"lodash.get": "^4.4.2",
|
"lodash.get": "^4.4.2",
|
||||||
"lodash.mapvalues": "^4.6.0",
|
"lodash.mapvalues": "^4.6.0",
|
||||||
"lodash.set": "^4.3.2",
|
"lodash.set": "^4.3.2",
|
||||||
|
"lru-cache": "^6.0.0",
|
||||||
"natsort": "^2.0.2",
|
"natsort": "^2.0.2",
|
||||||
"react-tabs": "^3.1.2"
|
"react-tabs": "^3.1.2"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,108 +0,0 @@
|
||||||
import {TickingPromise} from '@avocado/core';
|
|
||||||
import {compose, EventEmitter} from '@latus/core';
|
|
||||||
|
|
||||||
const decorate = compose(
|
|
||||||
EventEmitter,
|
|
||||||
);
|
|
||||||
|
|
||||||
class Actions {
|
|
||||||
|
|
||||||
constructor(expressions) {
|
|
||||||
this.expressions = 'function' === typeof expressions ? expressions() : expressions;
|
|
||||||
this._index = 0;
|
|
||||||
this.promise = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
emitFinished() {
|
|
||||||
this.emit('finished');
|
|
||||||
}
|
|
||||||
|
|
||||||
get index() {
|
|
||||||
return this._index;
|
|
||||||
}
|
|
||||||
|
|
||||||
set index(index) {
|
|
||||||
// Clear out the action promise.
|
|
||||||
this.promise = null;
|
|
||||||
this._index = index;
|
|
||||||
}
|
|
||||||
|
|
||||||
get() {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
tick(context, elapsed) {
|
|
||||||
// Empty resolves immediately.
|
|
||||||
if (this.expressions.length === 0) {
|
|
||||||
this.emitFinished();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// If the action promise ticks, tick it.
|
|
||||||
if (this.promise) {
|
|
||||||
if (this.promise instanceof TickingPromise) {
|
|
||||||
this.promise.tick(elapsed);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Actions execute immediately until a promise is made, or they're all executed.
|
|
||||||
// eslint-disable-next-line no-constant-condition
|
|
||||||
while (true) {
|
|
||||||
// Run the action.
|
|
||||||
const result = this.expressions[this.index](context);
|
|
||||||
// Deferred result.
|
|
||||||
if (result instanceof Promise) {
|
|
||||||
this.promise = result;
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
this.promise.catch(console.error).finally(() => this.prologue());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Immediate result.
|
|
||||||
this.prologue();
|
|
||||||
// Need to break out immediately if required.
|
|
||||||
if (0 === this.index) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
parallel(context) {
|
|
||||||
const results = this.expressions.map((expression) => expression(context));
|
|
||||||
for (let i = 0; i < results.length; i++) {
|
|
||||||
const result = results[i];
|
|
||||||
if (result instanceof TickingPromise) {
|
|
||||||
return TickingPromise.all(results);
|
|
||||||
}
|
|
||||||
if (result instanceof Promise) {
|
|
||||||
return Promise.all(results);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
prologue() {
|
|
||||||
// Increment and wrap the index.
|
|
||||||
this.index = (this.index + 1) % this.expressions.length;
|
|
||||||
// If rolled over, the actions are finished.
|
|
||||||
if (0 === this.index) {
|
|
||||||
this.emitFinished();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
serial(context) {
|
|
||||||
return this.tickingPromise(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
tickingPromise(context) {
|
|
||||||
return new TickingPromise(
|
|
||||||
(resolve) => {
|
|
||||||
this.once('finished', resolve);
|
|
||||||
},
|
|
||||||
(elapsed) => {
|
|
||||||
this.tick(context, elapsed);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default decorate(Actions);
|
|
|
@ -1,52 +0,0 @@
|
||||||
export function buildValue(value) {
|
|
||||||
if (
|
|
||||||
'object' === typeof value
|
|
||||||
&& -1 !== [
|
|
||||||
'condition',
|
|
||||||
'expression',
|
|
||||||
'expressions',
|
|
||||||
'literal',
|
|
||||||
].indexOf(value.type)
|
|
||||||
) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
type: 'literal',
|
|
||||||
value,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildCondition(operator, operands) {
|
|
||||||
return {
|
|
||||||
type: 'condition',
|
|
||||||
operator,
|
|
||||||
operands: operands.map((operand) => buildValue(operand)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildExpression(path, value) {
|
|
||||||
const expression = {
|
|
||||||
type: 'expression',
|
|
||||||
ops: path.map((key) => ({type: 'key', key})),
|
|
||||||
};
|
|
||||||
if ('undefined' !== typeof value) {
|
|
||||||
expression.value = buildValue(value);
|
|
||||||
}
|
|
||||||
return expression;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildExpressions(expressions) {
|
|
||||||
return {
|
|
||||||
type: 'expressions',
|
|
||||||
expressions: expressions.map((expression) => buildValue(expression)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildInvoke(path, args = []) {
|
|
||||||
const expression = buildExpression(path);
|
|
||||||
expression.ops.push({
|
|
||||||
type: 'invoke',
|
|
||||||
args: args.map((arg) => buildValue(arg)),
|
|
||||||
});
|
|
||||||
return expression;
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
let compilers;
|
|
||||||
|
|
||||||
function compilerFor(type, latus) {
|
|
||||||
if (!compilers) {
|
|
||||||
compilers = latus.get('%behaviorCompilers');
|
|
||||||
}
|
|
||||||
const {[type]: compiler} = compilers;
|
|
||||||
return compiler;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function compile(variant, latus) {
|
|
||||||
const compiler = compilerFor(variant.type, latus);
|
|
||||||
return compiler
|
|
||||||
? compiler(variant)
|
|
||||||
: () => Promise.reject(new TypeError(`No compiler for '${variant.type}', variant: ${variant}`));
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
import compile from './compile';
|
|
||||||
|
|
||||||
export default (latus) => (condition) => {
|
|
||||||
const {operator} = condition;
|
|
||||||
const operands = condition.operands.map((condition) => compile(condition, latus));
|
|
||||||
return (context) => {
|
|
||||||
switch (operator) {
|
|
||||||
case 'is':
|
|
||||||
return operands[0](context) === operands[1](context);
|
|
||||||
case 'isnt':
|
|
||||||
return operands[0](context) !== operands[1](context);
|
|
||||||
case '>':
|
|
||||||
return operands[0](context) > operands[1](context);
|
|
||||||
case '>=':
|
|
||||||
return operands[0](context) >= operands[1](context);
|
|
||||||
case '<':
|
|
||||||
return operands[0](context) < operands[1](context);
|
|
||||||
case '<=':
|
|
||||||
return operands[0](context) <= operands[1](context);
|
|
||||||
case 'or':
|
|
||||||
if (0 === operands.length) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
for (let i = 0; i < operands.length; i++) {
|
|
||||||
if (operands[i](context)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
case 'and':
|
|
||||||
if (0 === operands.length) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
for (let i = 0; i < operands.length; i++) {
|
|
||||||
if (!operands[i](context)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
case 'contains': {
|
|
||||||
const haystack = operands[0](context);
|
|
||||||
if (!Array.isArray(haystack)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const needle = operands[1](context);
|
|
||||||
return -1 !== haystack.indexOf(needle);
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
throw new TypeError(`Undefined operator '${operator}'`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,94 +0,0 @@
|
||||||
import {fastApply} from '@avocado/core';
|
|
||||||
|
|
||||||
import compile from './compile';
|
|
||||||
|
|
||||||
function compileOp(op, latus) {
|
|
||||||
let args;
|
|
||||||
if ('invoke' === op.type) {
|
|
||||||
args = op.args.map((arg) => (false === arg.compile ? arg : compile(arg, latus)));
|
|
||||||
}
|
|
||||||
return (context, previous, current) => {
|
|
||||||
switch (op.type) {
|
|
||||||
case 'key':
|
|
||||||
return current[op.key];
|
|
||||||
case 'invoke': {
|
|
||||||
// Pass the context itself as the last arg.
|
|
||||||
const evaluated = args.map((arg) => (
|
|
||||||
'function' === typeof arg ? arg(context) : arg
|
|
||||||
)).concat(context);
|
|
||||||
// Promises are resolved transparently.
|
|
||||||
const apply = (args) => fastApply(previous, current, args);
|
|
||||||
try {
|
|
||||||
return evaluated.some((arg) => arg instanceof Promise)
|
|
||||||
? Promise.all(evaluated).then(apply)
|
|
||||||
: apply(evaluated);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
throw new Error(`Behaving ${JSON.stringify(op, null, 2)}: ${error.stack}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
throw new TypeError(`Invalid expression op: '${op.type}'`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function disambiguateResult(value, fn) {
|
|
||||||
const apply = (value) => fn(value);
|
|
||||||
return value instanceof Promise ? value.then(apply) : apply(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default (latus) => (expression) => {
|
|
||||||
if (0 === expression.ops.length) {
|
|
||||||
return () => undefined;
|
|
||||||
}
|
|
||||||
const assign = expression.value && compile(expression.value, latus);
|
|
||||||
const ops = expression.ops.map((op) => compileOp(op, latus));
|
|
||||||
const {ops: rawOps} = expression;
|
|
||||||
const [, ...rest] = ops;
|
|
||||||
return (context) => {
|
|
||||||
let previous = null;
|
|
||||||
let current = context.get(rawOps[0].key);
|
|
||||||
if (0 === rest.length) {
|
|
||||||
if (assign) {
|
|
||||||
context.add(expression.ops[0].key, assign(context));
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return current;
|
|
||||||
}
|
|
||||||
for (let index = 0; index < rest.length; ++index) {
|
|
||||||
const op = rest[index];
|
|
||||||
// eslint-disable-next-line no-loop-func
|
|
||||||
current = disambiguateResult(current, (current) => {
|
|
||||||
let next;
|
|
||||||
const isLastOp = index === ops.length - 2;
|
|
||||||
if (!isLastOp || !assign) {
|
|
||||||
if ('undefined' === typeof current) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
next = op(context, previous, current);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const rawOp = rawOps[index + 1];
|
|
||||||
switch (rawOp.type) {
|
|
||||||
case 'key':
|
|
||||||
if ('object' === typeof current) {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
current[rawOp.key] = assign(context);
|
|
||||||
next = undefined;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'invoke':
|
|
||||||
next = undefined;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new TypeError(`Invalid expression op: '${op.type}'`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
previous = current;
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return current;
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,5 +0,0 @@
|
||||||
import compile from './compile';
|
|
||||||
|
|
||||||
export default (latus) => ({expressions}) => () => (
|
|
||||||
expressions.map((expression) => compile(expression, latus))
|
|
||||||
);
|
|
|
@ -1,11 +0,0 @@
|
||||||
import condition from './condition';
|
|
||||||
import expression from './expression';
|
|
||||||
import expressions from './expressions';
|
|
||||||
import literal from './literal';
|
|
||||||
|
|
||||||
export default (latus) => ({
|
|
||||||
condition: condition(latus),
|
|
||||||
expression: expression(latus),
|
|
||||||
expressions: expressions(latus),
|
|
||||||
literal: literal(latus),
|
|
||||||
});
|
|
|
@ -1 +0,0 @@
|
||||||
export default () => ({value}) => () => value;
|
|
|
@ -1,118 +0,0 @@
|
||||||
export default class Context {
|
|
||||||
|
|
||||||
constructor(defaults = {}, latus) {
|
|
||||||
this.latus = latus;
|
|
||||||
this.map = new Map();
|
|
||||||
this.clear();
|
|
||||||
this.addObjectMap(defaults);
|
|
||||||
}
|
|
||||||
|
|
||||||
add(key, value) {
|
|
||||||
if (key) {
|
|
||||||
this.map.set(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addObjectMap(map) {
|
|
||||||
Object.entries(map)
|
|
||||||
.forEach(([key, variable]) => (
|
|
||||||
this.add(key, variable)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
clear() {
|
|
||||||
this.destroy();
|
|
||||||
this.add('context', this);
|
|
||||||
this.addObjectMap(this.latus.get('%behaviorGlobals'));
|
|
||||||
}
|
|
||||||
|
|
||||||
clone() {
|
|
||||||
const clone = new Context({}, this.latus);
|
|
||||||
clone.map = new Map(this.map);
|
|
||||||
return clone;
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this
|
|
||||||
children() {
|
|
||||||
return {
|
|
||||||
add: {
|
|
||||||
label: "Add $2 to context as '$1'",
|
|
||||||
args: [
|
|
||||||
{
|
|
||||||
label: 'Key',
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Value',
|
|
||||||
type: 'any',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
type: 'void',
|
|
||||||
},
|
|
||||||
remove: {
|
|
||||||
args: [
|
|
||||||
{
|
|
||||||
label: 'Key',
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
type: 'void',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
describeChildren() {
|
|
||||||
return Array.from(this.map.entries())
|
|
||||||
.reduce(
|
|
||||||
(r, [key, value]) => ({
|
|
||||||
...r,
|
|
||||||
children: {
|
|
||||||
...r.children,
|
|
||||||
[key]: {
|
|
||||||
label: key,
|
|
||||||
type: this.constructor.descriptionFor(undefined, value, this.latus).type,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
{},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static descriptionFor(type, value, latus) {
|
|
||||||
const types = latus.get('%behavior-types');
|
|
||||||
if ('undefined' !== typeof type && types[type]) {
|
|
||||||
return {
|
|
||||||
children: types[type].children?.(value) || {},
|
|
||||||
type,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const entries = Object.entries(types);
|
|
||||||
for (let i = 0; i < entries.length; ++i) {
|
|
||||||
const [type, {children, infer}] = entries[i];
|
|
||||||
if (infer(value)) {
|
|
||||||
return {
|
|
||||||
children: children?.(value) || {},
|
|
||||||
type,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {type: 'undefined'};
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy() {
|
|
||||||
this.map.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
get(key) {
|
|
||||||
return this.map.get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
has(key) {
|
|
||||||
return this.map.has(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
remove(key) {
|
|
||||||
return this.map.delete(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,86 +0,0 @@
|
||||||
import {TickingPromise} from '@avocado/core';
|
|
||||||
|
|
||||||
import Actions from '../actions';
|
|
||||||
import compile from '../compilers/compile';
|
|
||||||
|
|
||||||
const serial = (expressions, context) => (new Actions(expressions)).serial(context);
|
|
||||||
|
|
||||||
export default {
|
|
||||||
|
|
||||||
conditional: (condition, expressions, context) => (
|
|
||||||
condition ? serial(expressions, context) : undefined
|
|
||||||
),
|
|
||||||
|
|
||||||
children: () => ({
|
|
||||||
conditional: {
|
|
||||||
type: 'void',
|
|
||||||
args: [
|
|
||||||
{
|
|
||||||
type: 'condition',
|
|
||||||
label: 'Condition',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'expressions',
|
|
||||||
label: 'Expressions',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
nop: {
|
|
||||||
type: 'void',
|
|
||||||
label: 'Idle',
|
|
||||||
args: [],
|
|
||||||
},
|
|
||||||
parallel: {
|
|
||||||
type: 'void',
|
|
||||||
label: 'Run expressions in parallel',
|
|
||||||
args: [
|
|
||||||
{
|
|
||||||
type: 'expressions',
|
|
||||||
label: 'Expressions',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
while: {
|
|
||||||
type: 'void',
|
|
||||||
label: 'Loop while condition',
|
|
||||||
args: [
|
|
||||||
{
|
|
||||||
compile: false,
|
|
||||||
type: 'condition',
|
|
||||||
label: 'Condition',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'expressions',
|
|
||||||
label: 'Expressions',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
|
|
||||||
nop: () => {},
|
|
||||||
|
|
||||||
parallel: (expressions, context) => (new Actions(expressions)).parallel(context),
|
|
||||||
|
|
||||||
serial,
|
|
||||||
|
|
||||||
while: (condition, expressions, context) => {
|
|
||||||
if (!compile(condition, context.get('latus'))(context)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
const actions = new Actions(expressions);
|
|
||||||
const ticker = actions.serial(context);
|
|
||||||
return new TickingPromise(
|
|
||||||
(resolve) => {
|
|
||||||
actions.on('finished', () => {
|
|
||||||
if (!compile(condition, context.get('latus'))(context)) {
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
(elapsed) => {
|
|
||||||
ticker.tick(elapsed);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
};
|
|
|
@ -1,10 +1,12 @@
|
||||||
import Flow from './flow';
|
import {TickingPromise} from '@avocado/core';
|
||||||
|
|
||||||
import Timing from './timing';
|
import Timing from './timing';
|
||||||
import Utility from './utility';
|
import Utility from './utility';
|
||||||
|
|
||||||
export default (latus) => ({
|
export default (latus) => ({
|
||||||
latus,
|
latus,
|
||||||
Flow,
|
SIDE: process.env.SIDE,
|
||||||
|
TickingPromise,
|
||||||
Timing,
|
Timing,
|
||||||
Utility,
|
Utility,
|
||||||
});
|
});
|
||||||
|
|
|
@ -76,30 +76,10 @@ export default {
|
||||||
get: (object, path, defaultValue) => get(object, path, defaultValue),
|
get: (object, path, defaultValue) => get(object, path, defaultValue),
|
||||||
|
|
||||||
log: (...args) => {
|
log: (...args) => {
|
||||||
// No context!
|
|
||||||
args.pop();
|
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(...args);
|
console.log(...args);
|
||||||
},
|
},
|
||||||
|
|
||||||
makeArray: (...args) => {
|
|
||||||
// No context!
|
|
||||||
args.pop();
|
|
||||||
return args;
|
|
||||||
},
|
|
||||||
|
|
||||||
makeObject: (...args) => {
|
|
||||||
// No context!
|
|
||||||
args.pop();
|
|
||||||
const object = {};
|
|
||||||
while (args.length > 0) {
|
|
||||||
const key = args.shift();
|
|
||||||
const value = args.shift();
|
|
||||||
object[key] = value;
|
|
||||||
}
|
|
||||||
return object;
|
|
||||||
},
|
|
||||||
|
|
||||||
merge: (l, r) => merge(l, r),
|
merge: (l, r) => merge(l, r),
|
||||||
|
|
||||||
set: (object, path, value) => set(object, path, value),
|
set: (object, path, value) => set(object, path, value),
|
||||||
|
|
|
@ -1,20 +1,13 @@
|
||||||
import {gatherWithLatus} from '@latus/core';
|
import {gatherWithLatus} from '@latus/core';
|
||||||
|
|
||||||
import compilers from './compilers';
|
|
||||||
import globals from './globals';
|
import globals from './globals';
|
||||||
import types from './types';
|
|
||||||
|
|
||||||
export {default as Actions} from './actions';
|
|
||||||
export * from './builders';
|
|
||||||
export * from './testers';
|
|
||||||
export {default as compile} from './compilers/compile';
|
|
||||||
export {default as Context} from './context';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
hooks: {
|
hooks: {
|
||||||
'@avocado/behavior/compilers': compilers,
|
|
||||||
'@avocado/behavior/globals': globals,
|
'@avocado/behavior/globals': globals,
|
||||||
'@avocado/behavior/types': types,
|
'@avocado/resource/resources': gatherWithLatus(
|
||||||
|
require.context('./resources', false, /\.js$/),
|
||||||
|
),
|
||||||
'@avocado/traits/traits': gatherWithLatus(
|
'@avocado/traits/traits': gatherWithLatus(
|
||||||
require.context('./traits', false, /\.js$/),
|
require.context('./traits', false, /\.js$/),
|
||||||
),
|
),
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
import './component.scss';
|
||||||
|
|
||||||
|
import {Code} from '@avocado/persea';
|
||||||
|
import {useJsonPatcher} from '@avocado/resource/persea';
|
||||||
|
import {
|
||||||
|
PropTypes,
|
||||||
|
React,
|
||||||
|
} from '@latus/react';
|
||||||
|
|
||||||
|
const ScriptComponent = ({path, resource}) => {
|
||||||
|
const patch = useJsonPatcher();
|
||||||
|
return (
|
||||||
|
<Code
|
||||||
|
code={resource}
|
||||||
|
name={path}
|
||||||
|
onChange={patch.onChange(path)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ScriptComponent.displayName = 'ScriptComponent';
|
||||||
|
|
||||||
|
ScriptComponent.propTypes = {
|
||||||
|
path: PropTypes.string.isRequired,
|
||||||
|
resource: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ScriptComponent;
|
|
@ -0,0 +1,3 @@
|
||||||
|
.text-renderer {
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
10
packages/behavior/src/persea/controllers/script/index.js
Normal file
10
packages/behavior/src/persea/controllers/script/index.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import {TextController} from '@avocado/resource/persea';
|
||||||
|
|
||||||
|
import Component from './component';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
Component,
|
||||||
|
matcher: /\.js$/,
|
||||||
|
fromBuffer: TextController.fromBuffer,
|
||||||
|
toBuffer: TextController.toBuffer,
|
||||||
|
};
|
|
@ -1,27 +1,16 @@
|
||||||
import {gatherComponents} from '@latus/react';
|
import {gatherComponents} from '@latus/react';
|
||||||
|
|
||||||
import Condition from './components/condition';
|
import ScriptController from './controllers/script';
|
||||||
import Expression from './components/expression';
|
|
||||||
import Expressions from './components/expressions';
|
|
||||||
import Literal from './components/literal';
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Condition,
|
ScriptController,
|
||||||
Expression,
|
|
||||||
Expressions,
|
|
||||||
Literal,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
hooks: {
|
hooks: {
|
||||||
'@latus/core/starting': async (latus) => {
|
'@avocado/resource/persea.controllers': () => [
|
||||||
latus.set('%behavior-components', {
|
ScriptController,
|
||||||
condition: Condition,
|
],
|
||||||
expression: Expression,
|
|
||||||
expressions: Expressions,
|
|
||||||
literal: Literal,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
'@avocado/traits/components': gatherComponents(
|
'@avocado/traits/components': gatherComponents(
|
||||||
require.context('./traits', false, /\.jsx$/),
|
require.context('./traits', false, /\.jsx$/),
|
||||||
),
|
),
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {
|
||||||
PropTypes,
|
PropTypes,
|
||||||
React,
|
React,
|
||||||
} from '@latus/react';
|
} from '@latus/react';
|
||||||
import {Number} from '@avocado/persea';
|
import {Code} from '@avocado/persea';
|
||||||
import {
|
import {
|
||||||
Tab,
|
Tab,
|
||||||
Tabs,
|
Tabs,
|
||||||
|
@ -16,11 +16,7 @@ import {
|
||||||
TabPanel,
|
TabPanel,
|
||||||
} from 'react-tabs';
|
} from 'react-tabs';
|
||||||
|
|
||||||
import Expression from '../components/expression';
|
|
||||||
import Expressions from '../components/expressions';
|
|
||||||
|
|
||||||
const Behaved = ({
|
const Behaved = ({
|
||||||
entity,
|
|
||||||
json,
|
json,
|
||||||
path,
|
path,
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -29,113 +25,36 @@ const Behaved = ({
|
||||||
<div className="behaved">
|
<div className="behaved">
|
||||||
<Tabs>
|
<Tabs>
|
||||||
<TabList>
|
<TabList>
|
||||||
<Tab>Daemons</Tab>
|
|
||||||
<Tab>Routines</Tab>
|
<Tab>Routines</Tab>
|
||||||
<Tab>Collectives</Tab>
|
<Tab>Daemons</Tab>
|
||||||
</TabList>
|
</TabList>
|
||||||
<div className="behaved__tab-panels">
|
<div className="behaved__tab-panels">
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
<JsonTabs
|
<JsonTabs
|
||||||
createPanel={(expressions, key) => (
|
createPanel={(code, key) => (
|
||||||
<Expressions
|
<Code
|
||||||
context={entity.context}
|
code={code}
|
||||||
value={expressions}
|
name={join(path, 'params/routines', key)}
|
||||||
path={join(path, 'params/daemons', key)}
|
onChange={patch.onChange(join(path, 'params/routines', key))}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
defaultValue={{
|
defaultValue=""
|
||||||
type: 'expressions',
|
|
||||||
expressions: [],
|
|
||||||
}}
|
|
||||||
path={join(path, 'params/daemons')}
|
|
||||||
map={json.params.daemons}
|
|
||||||
/>
|
|
||||||
</TabPanel>
|
|
||||||
<TabPanel>
|
|
||||||
<JsonTabs
|
|
||||||
createPanel={(expressions, key) => (
|
|
||||||
<Expressions
|
|
||||||
context={entity.context}
|
|
||||||
value={expressions}
|
|
||||||
path={join(path, 'params/routines', key)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
defaultValue={{
|
|
||||||
type: 'expressions',
|
|
||||||
expressions: [],
|
|
||||||
}}
|
|
||||||
path={join(path, 'params/routines')}
|
path={join(path, 'params/routines')}
|
||||||
map={json.params.routines}
|
map={json.params.routines}
|
||||||
/>
|
/>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
<JsonTabs
|
<JsonTabs
|
||||||
createPanel={(
|
createPanel={(code, key) => (
|
||||||
{
|
<Code
|
||||||
find,
|
code={code}
|
||||||
max,
|
name={join(path, 'params/daemons', key)}
|
||||||
reset,
|
onChange={patch.onChange(join(path, 'params/daemons', key))}
|
||||||
threshold,
|
/>
|
||||||
},
|
|
||||||
key,
|
|
||||||
) => (
|
|
||||||
<>
|
|
||||||
<div className="label">
|
|
||||||
<div className="vertical">Find</div>
|
|
||||||
<Expression
|
|
||||||
context={entity.context}
|
|
||||||
value={find}
|
|
||||||
path={join(path, 'params/collectives', key, 'find')}
|
|
||||||
type="array"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="label">
|
|
||||||
<div className="vertical">Reset</div>
|
|
||||||
<Expressions
|
|
||||||
context={entity.context}
|
|
||||||
value={reset}
|
|
||||||
path={join(path, 'params/collectives', key, 'reset')}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="label">
|
|
||||||
<div>Threshold</div>
|
|
||||||
<Number
|
|
||||||
onChange={patch.onChange(join(path, 'params/collectives', key, 'threshold'))}
|
|
||||||
value={threshold}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="label">
|
|
||||||
<div>Max</div>
|
|
||||||
<Number
|
|
||||||
onChange={patch.onChange(join(path, 'params/collectives', key, 'max'))}
|
|
||||||
value={max}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
defaultValue={{
|
defaultValue=""
|
||||||
find: {
|
path={join(path, 'params/daemons')}
|
||||||
type: 'expression',
|
map={json.params.daemons}
|
||||||
ops: [
|
|
||||||
{
|
|
||||||
type: 'key',
|
|
||||||
key: 'entity',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'key',
|
|
||||||
key: 'list',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
max: 128,
|
|
||||||
reset: {
|
|
||||||
type: 'expressions',
|
|
||||||
expressions: [],
|
|
||||||
},
|
|
||||||
threshold: 1,
|
|
||||||
}}
|
|
||||||
path={join(path, 'params/collectives')}
|
|
||||||
map={json.params.collectives}
|
|
||||||
/>
|
/>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</div>
|
</div>
|
||||||
|
@ -147,12 +66,9 @@ const Behaved = ({
|
||||||
Behaved.displayName = 'Behaved';
|
Behaved.displayName = 'Behaved';
|
||||||
|
|
||||||
Behaved.propTypes = {
|
Behaved.propTypes = {
|
||||||
entity: PropTypes.shape({
|
entity: PropTypes.shape({}).isRequired,
|
||||||
context: PropTypes.shape({}),
|
|
||||||
}).isRequired,
|
|
||||||
json: PropTypes.shape({
|
json: PropTypes.shape({
|
||||||
params: PropTypes.shape({
|
params: PropTypes.shape({
|
||||||
collectives: PropTypes.shape({}),
|
|
||||||
daemons: PropTypes.shape({}),
|
daemons: PropTypes.shape({}),
|
||||||
routines: PropTypes.shape({}),
|
routines: PropTypes.shape({}),
|
||||||
}),
|
}),
|
||||||
|
|
180
packages/behavior/src/resources/script.js
Normal file
180
packages/behavior/src/resources/script.js
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
import {TickingPromise} from '@avocado/core';
|
||||||
|
import {Resource} from '@avocado/resource';
|
||||||
|
import {Sandbox} from '@avocado/sandbox';
|
||||||
|
import {parse} from '@babel/parser';
|
||||||
|
import {compose, EventEmitter} from '@latus/core';
|
||||||
|
import LRU from 'lru-cache';
|
||||||
|
|
||||||
|
const cache = new LRU({
|
||||||
|
max: 128,
|
||||||
|
maxAge: Infinity,
|
||||||
|
});
|
||||||
|
|
||||||
|
const empty = {
|
||||||
|
type: 'File',
|
||||||
|
program: {
|
||||||
|
type: 'Program',
|
||||||
|
sourceType: 'module',
|
||||||
|
body: [],
|
||||||
|
directives: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (latus) => {
|
||||||
|
const decorate = compose(
|
||||||
|
EventEmitter,
|
||||||
|
);
|
||||||
|
return class Script extends decorate(Resource) {
|
||||||
|
|
||||||
|
constructor(sandbox) {
|
||||||
|
super();
|
||||||
|
this.sandbox = sandbox || new Sandbox(empty);
|
||||||
|
this.promise = null;
|
||||||
|
this.on('finished', this.reset, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
get context() {
|
||||||
|
return this.sandbox.context;
|
||||||
|
}
|
||||||
|
|
||||||
|
set context(context) {
|
||||||
|
this.sandbox.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
static createContext(locals = {}) {
|
||||||
|
return {
|
||||||
|
...latus.get('%behaviorGlobals'),
|
||||||
|
...locals,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async evaluate(callback) {
|
||||||
|
this.sandbox.reset();
|
||||||
|
let {done, value} = this.sandbox.next();
|
||||||
|
if (value instanceof Promise) {
|
||||||
|
await value;
|
||||||
|
}
|
||||||
|
while (!done) {
|
||||||
|
({done, value} = this.sandbox.next());
|
||||||
|
if (value instanceof Promise) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (value instanceof Promise) {
|
||||||
|
value.then(callback);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
callback(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromCode(code, context) {
|
||||||
|
let ast;
|
||||||
|
if (cache.has(code)) {
|
||||||
|
ast = cache.get(code);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cache.set(code, ast = this.parse(code));
|
||||||
|
}
|
||||||
|
return new this(new Sandbox(ast, this.createContext(context || {})));
|
||||||
|
}
|
||||||
|
|
||||||
|
static async load(uriOrCode, context) {
|
||||||
|
if ('/'.charCodeAt(0) === uriOrCode.charCodeAt(0)) {
|
||||||
|
const script = await super.load(uriOrCode);
|
||||||
|
script.context = this.createContext(context || {});
|
||||||
|
return script;
|
||||||
|
}
|
||||||
|
return this.fromCode(uriOrCode, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
static loadTickingPromise(uriOrCode, context) {
|
||||||
|
let tickingPromise;
|
||||||
|
return new TickingPromise(
|
||||||
|
(resolve) => {
|
||||||
|
this.load(uriOrCode, context).then((script) => {
|
||||||
|
tickingPromise = script.tickingPromise();
|
||||||
|
resolve(tickingPromise);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(elapsed) => {
|
||||||
|
tickingPromise?.tick?.(elapsed);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async load(buffer, uri) {
|
||||||
|
if (!buffer) {
|
||||||
|
this.ast = new Sandbox(empty);
|
||||||
|
}
|
||||||
|
if (cache.has(uri)) {
|
||||||
|
this.sandbox = new Sandbox(cache.get(uri));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const ast = this.constructor.parse(Buffer.from(buffer).toString('utf8'));
|
||||||
|
cache.set(uri, ast);
|
||||||
|
this.sandbox = new Sandbox(ast);
|
||||||
|
}
|
||||||
|
|
||||||
|
static parse(script) {
|
||||||
|
return parse(
|
||||||
|
script,
|
||||||
|
{
|
||||||
|
allowAwaitOutsideFunction: true,
|
||||||
|
allowReturnOutsideFunction: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.promise = null;
|
||||||
|
this.sandbox.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
tick(elapsed) {
|
||||||
|
if (this.promise) {
|
||||||
|
if (this.promise instanceof TickingPromise) {
|
||||||
|
this.promise.tick(elapsed);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line no-constant-condition
|
||||||
|
while (true) {
|
||||||
|
const {done, value} = this.sandbox.next();
|
||||||
|
if (value) {
|
||||||
|
const {value: result} = value;
|
||||||
|
if (result instanceof Promise) {
|
||||||
|
result
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
.catch(console.error)
|
||||||
|
.finally(() => {
|
||||||
|
if (done) {
|
||||||
|
this.emit('finished');
|
||||||
|
}
|
||||||
|
this.promise = null;
|
||||||
|
});
|
||||||
|
this.promise = result;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (done) {
|
||||||
|
this.emit('finished');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tickingPromise() {
|
||||||
|
return new TickingPromise(
|
||||||
|
(resolve) => {
|
||||||
|
this.once('finished', resolve);
|
||||||
|
},
|
||||||
|
(elapsed) => {
|
||||||
|
this.tick(elapsed);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
};
|
3
packages/behavior/src/sandbox.js
Normal file
3
packages/behavior/src/sandbox.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import {Sandbox} from '@avocado/sandbox';
|
||||||
|
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
export const isInvocation = (op) => 'invoke' === op.type;
|
|
||||||
|
|
||||||
export const isKey = (op) => 'key' === op.type;
|
|
|
@ -1,10 +1,6 @@
|
||||||
|
import {mapValuesAsync} from '@avocado/core';
|
||||||
import {StateProperty, Trait} from '@avocado/traits';
|
import {StateProperty, Trait} from '@avocado/traits';
|
||||||
import {compose} from '@latus/core';
|
import {compose} from '@latus/core';
|
||||||
import mapValues from 'lodash.mapvalues';
|
|
||||||
|
|
||||||
import Actions from '../actions';
|
|
||||||
import compile from '../compilers/compile';
|
|
||||||
import Context from '../context';
|
|
||||||
|
|
||||||
const decorate = compose(
|
const decorate = compose(
|
||||||
StateProperty('activeCollective'),
|
StateProperty('activeCollective'),
|
||||||
|
@ -22,7 +18,7 @@ export default (latus) => class Behaved extends decorate(Trait) {
|
||||||
|
|
||||||
#collectives = [];
|
#collectives = [];
|
||||||
|
|
||||||
#context = new Context({}, latus);
|
#context = {};
|
||||||
|
|
||||||
#currentRoutine = '';
|
#currentRoutine = '';
|
||||||
|
|
||||||
|
@ -35,6 +31,8 @@ export default (latus) => class Behaved extends decorate(Trait) {
|
||||||
({
|
({
|
||||||
currentRoutine: this.#currentRoutine,
|
currentRoutine: this.#currentRoutine,
|
||||||
} = this.constructor.defaultState());
|
} = this.constructor.defaultState());
|
||||||
|
const {Script} = latus.get('%resources');
|
||||||
|
this.#context = Script.createContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
get context() {
|
get context() {
|
||||||
|
@ -43,6 +41,7 @@ export default (latus) => class Behaved extends decorate(Trait) {
|
||||||
|
|
||||||
static defaultParams() {
|
static defaultParams() {
|
||||||
return {
|
return {
|
||||||
|
codeRoutines: {},
|
||||||
collectives: {},
|
collectives: {},
|
||||||
daemons: {},
|
daemons: {},
|
||||||
routines: {},
|
routines: {},
|
||||||
|
@ -114,7 +113,7 @@ export default (latus) => class Behaved extends decorate(Trait) {
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
super.destroy();
|
super.destroy();
|
||||||
this.#context.destroy();
|
this.#context = {};
|
||||||
this.#currentRoutine = undefined;
|
this.#currentRoutine = undefined;
|
||||||
this.#routines = undefined;
|
this.#routines = undefined;
|
||||||
}
|
}
|
||||||
|
@ -133,38 +132,27 @@ export default (latus) => class Behaved extends decorate(Trait) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async loadScripts(scripts, context) {
|
||||||
|
return mapValuesAsync(scripts, async (codeOrUri) => {
|
||||||
|
const {Script} = latus.get('%resources');
|
||||||
|
const script = await Script.load(codeOrUri);
|
||||||
|
script.context = context;
|
||||||
|
return script;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async load(json) {
|
async load(json) {
|
||||||
await super.load(json);
|
await super.load(json);
|
||||||
this.#context = new Context(
|
const {Script} = latus.get('%resources');
|
||||||
{
|
this.#context = Script.createContext({
|
||||||
entity: this.entity,
|
entity: this.entity,
|
||||||
},
|
});
|
||||||
latus,
|
const daemons = {
|
||||||
);
|
...this.entity.invokeHookReduced('daemons'),
|
||||||
this.#daemons = Object.values(this.params.daemons)
|
...this.params.daemons,
|
||||||
.map((daemon) => new Actions(compile(daemon, latus)));
|
};
|
||||||
this.#routines = mapValues(
|
this.#daemons = Object.values(await this.constructor.loadScripts(daemons, this.#context));
|
||||||
this.params.routines,
|
this.#routines = await this.constructor.loadScripts(this.params.routines, this.#context);
|
||||||
(routine) => new Actions(compile(routine, latus)),
|
|
||||||
);
|
|
||||||
this.#collectives = Object.entries(this.params.collectives)
|
|
||||||
.map(([
|
|
||||||
key,
|
|
||||||
{
|
|
||||||
find,
|
|
||||||
max,
|
|
||||||
reset,
|
|
||||||
threshold,
|
|
||||||
},
|
|
||||||
]) => [
|
|
||||||
key,
|
|
||||||
{
|
|
||||||
find: compile(find, latus),
|
|
||||||
max,
|
|
||||||
reset: compile(reset, latus),
|
|
||||||
threshold,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
this.updateCurrentRoutine(this.state.currentRoutine);
|
this.updateCurrentRoutine(this.state.currentRoutine);
|
||||||
super.isBehaving = 'client' !== process.env.SIDE;
|
super.isBehaving = 'client' !== process.env.SIDE;
|
||||||
}
|
}
|
||||||
|
@ -172,11 +160,6 @@ export default (latus) => class Behaved extends decorate(Trait) {
|
||||||
methods() {
|
methods() {
|
||||||
return {
|
return {
|
||||||
|
|
||||||
jumpToRoutine: (routine, index) => {
|
|
||||||
this.updateCurrentRoutine(routine);
|
|
||||||
this.#currentRoutine.index = 'number' === typeof index ? index : 0;
|
|
||||||
},
|
|
||||||
|
|
||||||
leaveCollection: () => {
|
leaveCollection: () => {
|
||||||
this.entity.activeCollective = null;
|
this.entity.activeCollective = null;
|
||||||
},
|
},
|
||||||
|
@ -189,53 +172,19 @@ export default (latus) => class Behaved extends decorate(Trait) {
|
||||||
this.#accumulator += elapsed;
|
this.#accumulator += elapsed;
|
||||||
if (this.#accumulator >= STATIC_INTERVAL) {
|
if (this.#accumulator >= STATIC_INTERVAL) {
|
||||||
for (let i = 0; i < this.#daemons.length; ++i) {
|
for (let i = 0; i < this.#daemons.length; ++i) {
|
||||||
this.#daemons[i].tick(this.#context, elapsed);
|
this.#daemons[i].tick(elapsed);
|
||||||
}
|
|
||||||
for (let i = 0; i < this.#collectives.length; ++i) {
|
|
||||||
const [
|
|
||||||
key,
|
|
||||||
{
|
|
||||||
find,
|
|
||||||
max,
|
|
||||||
reset,
|
|
||||||
threshold,
|
|
||||||
},
|
|
||||||
] = this.#collectives[i];
|
|
||||||
if (this.entity.activeCollective && this.entity.activeCollective !== key) {
|
|
||||||
// eslint-disable-next-line no-continue
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const crew = find(this.#context);
|
|
||||||
const isInCrew = -1 !== crew.indexOf(this.entity);
|
|
||||||
const hasLength = (
|
|
||||||
!this.entity.activeCollective
|
|
||||||
|| 'threshold' === this.entity.context.get('crew').length
|
|
||||||
)
|
|
||||||
? crew.length >= threshold
|
|
||||||
: crew.length > this.entity.context.get('crew').length;
|
|
||||||
if (isInCrew && hasLength && crew.length < max) {
|
|
||||||
crew.forEach((entity, i) => {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
entity.activeCollective = key;
|
|
||||||
const {context} = entity;
|
|
||||||
context.add('crew', crew);
|
|
||||||
context.add('index', i);
|
|
||||||
reset().forEach((expr) => {
|
|
||||||
expr(context);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
this.#accumulator -= STATIC_INTERVAL;
|
this.#accumulator -= STATIC_INTERVAL;
|
||||||
}
|
}
|
||||||
if (this.#currentRoutine) {
|
if (this.#currentRoutine) {
|
||||||
this.#currentRoutine.tick(this.#context, elapsed);
|
this.#currentRoutine.tick(elapsed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCurrentRoutine(currentRoutine) {
|
updateCurrentRoutine(currentRoutine) {
|
||||||
this.#currentRoutine = this.#routines[currentRoutine];
|
this.#currentRoutine = this.#routines[currentRoutine];
|
||||||
|
this.#currentRoutine.reset();
|
||||||
super.currentRoutine = currentRoutine;
|
super.currentRoutine = currentRoutine;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,91 +0,0 @@
|
||||||
import Context from './context';
|
|
||||||
import Flow from './globals/flow';
|
|
||||||
import Timing from './globals/timing';
|
|
||||||
import Utility from './globals/utility';
|
|
||||||
|
|
||||||
export default () => ({
|
|
||||||
array: {
|
|
||||||
children: () => ({
|
|
||||||
length: {
|
|
||||||
type: 'number',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
create: (v) => ({type: 'literal', value: 'undefined' === typeof v ? [] : v}),
|
|
||||||
infer: (v) => Array.isArray(v),
|
|
||||||
},
|
|
||||||
bool: {
|
|
||||||
create: (v) => ({type: 'literal', value: 'undefined' === typeof v ? false : v}),
|
|
||||||
infer: (v) => 'boolean' === typeof v,
|
|
||||||
},
|
|
||||||
condition: {
|
|
||||||
create: () => ({
|
|
||||||
type: 'condition',
|
|
||||||
operator: 'is',
|
|
||||||
operands: [
|
|
||||||
{
|
|
||||||
type: 'literal',
|
|
||||||
value: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'literal',
|
|
||||||
value: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
infer: (v) => 'condition' === v?.type,
|
|
||||||
},
|
|
||||||
Context: {
|
|
||||||
children: Context.prototype.children,
|
|
||||||
infer: (v) => v?.children === Context.prototype.children,
|
|
||||||
},
|
|
||||||
expression: {
|
|
||||||
create: () => ({type: 'expression', ops: []}),
|
|
||||||
infer: (v) => 'expression' === v?.type,
|
|
||||||
},
|
|
||||||
expressions: {
|
|
||||||
create: () => ({type: 'expressions', expressions: []}),
|
|
||||||
infer: (v) => 'expressions' === v?.type,
|
|
||||||
},
|
|
||||||
flow: {
|
|
||||||
children: Flow.children,
|
|
||||||
infer: (v) => v?.children === Flow.children,
|
|
||||||
},
|
|
||||||
function: {
|
|
||||||
children: () => ({
|
|
||||||
name: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
create: () => null,
|
|
||||||
infer: (v) => 'function' === typeof v,
|
|
||||||
},
|
|
||||||
number: {
|
|
||||||
create: (v) => ({type: 'literal', value: 'undefined' === typeof v ? 0 : v}),
|
|
||||||
infer: (v) => 'number' === typeof v,
|
|
||||||
},
|
|
||||||
timing: {
|
|
||||||
children: Timing.children,
|
|
||||||
infer: (v) => v?.children === Timing.children,
|
|
||||||
},
|
|
||||||
utility: {
|
|
||||||
children: Utility.children,
|
|
||||||
infer: (v) => v?.children === Utility.children,
|
|
||||||
},
|
|
||||||
object: {
|
|
||||||
create: () => ({type: 'literal', value: '{}'}),
|
|
||||||
infer: (v) => 'object' === typeof v,
|
|
||||||
},
|
|
||||||
string: {
|
|
||||||
children: () => ({
|
|
||||||
length: {
|
|
||||||
type: 'number',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
create: (v) => ({type: 'literal', value: 'undefined' === typeof v ? '' : v}),
|
|
||||||
infer: (v) => 'string' === typeof v,
|
|
||||||
},
|
|
||||||
undefined: {
|
|
||||||
create: () => ({type: 'literal'}),
|
|
||||||
infer: () => true,
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,146 +0,0 @@
|
||||||
import {Latus} from '@latus/core';
|
|
||||||
import {expect} from 'chai';
|
|
||||||
|
|
||||||
import {
|
|
||||||
buildExpression,
|
|
||||||
buildExpressions,
|
|
||||||
buildInvoke,
|
|
||||||
buildValue,
|
|
||||||
} from '../src/builders';
|
|
||||||
|
|
||||||
const asyncWait = () => new Promise((resolve) => setTimeout(resolve, 0));
|
|
||||||
|
|
||||||
let latus;
|
|
||||||
let Entity;
|
|
||||||
beforeEach(async () => {
|
|
||||||
latus = Latus.mock({
|
|
||||||
'@avocado/behavior': require('../src'),
|
|
||||||
'@avocado/entity': require('@avocado/entity'),
|
|
||||||
'@avocado/resource': require('@avocado/resource'),
|
|
||||||
'@avocado/traits': require('@avocado/traits'),
|
|
||||||
});
|
|
||||||
await Promise.all(latus.invokeFlat('@latus/core/starting'));
|
|
||||||
({Entity} = latus.get('%resources'));
|
|
||||||
});
|
|
||||||
describe('Behaved', () => {
|
|
||||||
let entity;
|
|
||||||
beforeEach(async () => {
|
|
||||||
entity = await Entity.load({
|
|
||||||
traits: {
|
|
||||||
Behaved: {
|
|
||||||
params: {
|
|
||||||
routines: {
|
|
||||||
initial: buildExpressions([
|
|
||||||
buildInvoke(
|
|
||||||
['entity', 'forceMovement'],
|
|
||||||
[
|
|
||||||
buildValue([10, 0]),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
buildInvoke(
|
|
||||||
['Timing', 'wait'],
|
|
||||||
[
|
|
||||||
buildValue(1),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
another: buildExpressions([
|
|
||||||
buildInvoke(
|
|
||||||
['entity', 'forceMovement'],
|
|
||||||
[
|
|
||||||
buildValue([20, 0]),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
one: buildExpressions([
|
|
||||||
buildInvoke(
|
|
||||||
['entity', 'forceMovement'],
|
|
||||||
[
|
|
||||||
buildValue([30, 0]),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
buildInvoke(
|
|
||||||
['Timing', 'wait'],
|
|
||||||
[
|
|
||||||
buildValue(0),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
buildInvoke(
|
|
||||||
['entity', 'jumpToRoutine'],
|
|
||||||
[
|
|
||||||
buildValue('two'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
two: buildExpressions([
|
|
||||||
buildInvoke(
|
|
||||||
['entity', 'forceMovement'],
|
|
||||||
[
|
|
||||||
buildValue([40, 0]),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
buildInvoke(
|
|
||||||
['entity', 'jumpToRoutine'],
|
|
||||||
[
|
|
||||||
buildValue('one'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Mobile: {},
|
|
||||||
Positioned: {},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('exists', async () => {
|
|
||||||
expect(entity.is('Behaved')).to.be.true;
|
|
||||||
});
|
|
||||||
if ('client' !== process.env.SIDE) {
|
|
||||||
it('runs routines', async () => {
|
|
||||||
expect(entity.position).to.deep.equal([0, 0]);
|
|
||||||
entity.tick(0);
|
|
||||||
await asyncWait();
|
|
||||||
expect(entity.position).to.deep.equal([10, 0]);
|
|
||||||
entity.tick(1);
|
|
||||||
await asyncWait();
|
|
||||||
expect(entity.position).to.deep.equal([10, 0]);
|
|
||||||
entity.isBehaving = false;
|
|
||||||
entity.tick(1);
|
|
||||||
await asyncWait();
|
|
||||||
expect(entity.position).to.deep.equal([10, 0]);
|
|
||||||
entity.isBehaving = true;
|
|
||||||
entity.tick(1);
|
|
||||||
await asyncWait();
|
|
||||||
expect(entity.position).to.deep.equal([20, 0]);
|
|
||||||
});
|
|
||||||
it('can change routines', async () => {
|
|
||||||
entity.currentRoutine = 'another';
|
|
||||||
expect(entity.position).to.deep.equal([0, 0]);
|
|
||||||
entity.tick(0);
|
|
||||||
await asyncWait();
|
|
||||||
expect(entity.position).to.deep.equal([20, 0]);
|
|
||||||
});
|
|
||||||
it('allows routines to set routine', async () => {
|
|
||||||
entity.currentRoutine = 'one';
|
|
||||||
expect(entity.position).to.deep.equal([0, 0]);
|
|
||||||
entity.tick(0);
|
|
||||||
await asyncWait();
|
|
||||||
expect(entity.currentRoutine).to.equal('one');
|
|
||||||
expect(entity.position).to.deep.equal([30, 0]);
|
|
||||||
entity.tick(0);
|
|
||||||
await asyncWait();
|
|
||||||
expect(entity.currentRoutine).to.equal('one');
|
|
||||||
expect(entity.position).to.deep.equal([30, 0]);
|
|
||||||
entity.tick(0);
|
|
||||||
await asyncWait();
|
|
||||||
expect(entity.currentRoutine).to.equal('two');
|
|
||||||
expect(entity.position).to.deep.equal([30, 0]);
|
|
||||||
entity.tick(0);
|
|
||||||
await asyncWait();
|
|
||||||
expect(entity.currentRoutine).to.equal('one');
|
|
||||||
expect(entity.position).to.deep.equal([70, 0]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,32 +0,0 @@
|
||||||
import {Latus} from '@latus/core';
|
|
||||||
import {expect} from 'chai';
|
|
||||||
|
|
||||||
import {
|
|
||||||
buildCondition,
|
|
||||||
buildExpression,
|
|
||||||
buildValue,
|
|
||||||
} from '../src/builders';
|
|
||||||
import Context from '../src/context';
|
|
||||||
import compile from '../src/compilers/compile';
|
|
||||||
|
|
||||||
let latus;
|
|
||||||
let context;
|
|
||||||
beforeEach(async () => {
|
|
||||||
latus = Latus.mock({
|
|
||||||
'@avocado/behavior': require('../src'),
|
|
||||||
});
|
|
||||||
await Promise.all(latus.invokeFlat('@latus/core/starting'));
|
|
||||||
context = new Context({}, latus);
|
|
||||||
});
|
|
||||||
it('builds values', async () => {
|
|
||||||
const passthroughs = [
|
|
||||||
buildCondition('is', []),
|
|
||||||
buildExpression([]),
|
|
||||||
buildValue(69),
|
|
||||||
];
|
|
||||||
for (let i = 0; i < passthroughs.length; i++) {
|
|
||||||
const passthrough = passthroughs[i];
|
|
||||||
expect(passthrough).to.equal(buildValue(passthrough));
|
|
||||||
}
|
|
||||||
expect(buildValue(420)).to.deep.equal({type: 'literal', value: 420});
|
|
||||||
});
|
|
|
@ -1,111 +0,0 @@
|
||||||
import {Latus} from '@latus/core';
|
|
||||||
import {expect} from 'chai';
|
|
||||||
|
|
||||||
import {
|
|
||||||
buildCondition,
|
|
||||||
buildValue,
|
|
||||||
} from '../src/builders';
|
|
||||||
import Context from '../src/context';
|
|
||||||
import compile from '../src/compilers/compile';
|
|
||||||
|
|
||||||
let latus;
|
|
||||||
let context;
|
|
||||||
beforeEach(async () => {
|
|
||||||
latus = Latus.mock({
|
|
||||||
'@avocado/behavior': require('../src'),
|
|
||||||
});
|
|
||||||
await Promise.all(latus.invokeFlat('@latus/core/starting'));
|
|
||||||
context = new Context({}, latus);
|
|
||||||
});
|
|
||||||
describe('Condition', () => {
|
|
||||||
it('has equality operator', async () => {
|
|
||||||
const condition = compile(buildCondition('is', [
|
|
||||||
buildValue(69),
|
|
||||||
buildValue(69),
|
|
||||||
]), latus);
|
|
||||||
expect(condition(context)).to.be.true;
|
|
||||||
});
|
|
||||||
it('has inequality operator', async () => {
|
|
||||||
const condition = compile(buildCondition('isnt', [
|
|
||||||
buildValue(69),
|
|
||||||
buildValue(68),
|
|
||||||
]), latus);
|
|
||||||
expect(condition(context)).to.be.true;
|
|
||||||
});
|
|
||||||
it('has greater than operator', async () => {
|
|
||||||
const condition = compile(buildCondition('>', [
|
|
||||||
buildValue(69),
|
|
||||||
buildValue(68),
|
|
||||||
]), latus);
|
|
||||||
expect(condition(context)).to.be.true;
|
|
||||||
});
|
|
||||||
it('has greater than or equal operator', async () => {
|
|
||||||
expect(compile(buildCondition('>=', [
|
|
||||||
buildValue(69),
|
|
||||||
buildValue(69),
|
|
||||||
]), latus)(context)).to.be.true;
|
|
||||||
expect(compile(buildCondition('>=', [
|
|
||||||
buildValue(69),
|
|
||||||
buildValue(68),
|
|
||||||
]), latus)(context)).to.be.true;
|
|
||||||
});
|
|
||||||
it('has less than operator', async () => {
|
|
||||||
const condition = compile(buildCondition('<', [
|
|
||||||
buildValue(69),
|
|
||||||
buildValue(70),
|
|
||||||
]), latus);
|
|
||||||
expect(condition(context)).to.be.true;
|
|
||||||
});
|
|
||||||
it('has less than or equal operator', async () => {
|
|
||||||
expect(compile(buildCondition('<=', [
|
|
||||||
buildValue(69),
|
|
||||||
buildValue(69),
|
|
||||||
]), latus)(context)).to.be.true;
|
|
||||||
expect(compile(buildCondition('<=', [
|
|
||||||
buildValue(69),
|
|
||||||
buildValue(70),
|
|
||||||
]), latus)(context)).to.be.true;
|
|
||||||
});
|
|
||||||
it('has or operator', async () => {
|
|
||||||
expect(compile(buildCondition('or', [
|
|
||||||
buildValue(true),
|
|
||||||
buildValue(false),
|
|
||||||
]), latus)(context)).to.be.true;
|
|
||||||
expect(compile(buildCondition('or', [
|
|
||||||
buildValue(true),
|
|
||||||
buildValue(true),
|
|
||||||
]), latus)(context)).to.be.true;
|
|
||||||
expect(compile(buildCondition('or', [
|
|
||||||
buildValue(false),
|
|
||||||
buildValue(false),
|
|
||||||
]), latus)(context)).to.be.false;
|
|
||||||
});
|
|
||||||
it('has and operator', async () => {
|
|
||||||
expect(compile(buildCondition('and', [
|
|
||||||
buildValue(true),
|
|
||||||
buildValue(false),
|
|
||||||
]), latus)(context)).to.be.false;
|
|
||||||
expect(compile(buildCondition('and', [
|
|
||||||
buildValue(true),
|
|
||||||
buildValue(true),
|
|
||||||
]), latus)(context)).to.be.true;
|
|
||||||
expect(compile(buildCondition('and', [
|
|
||||||
buildValue(false),
|
|
||||||
buildValue(false),
|
|
||||||
]), latus)(context)).to.be.false;
|
|
||||||
});
|
|
||||||
it('has contains operator', async () => {
|
|
||||||
expect(compile(buildCondition('contains', [
|
|
||||||
buildValue([1, 2, 3, 69]),
|
|
||||||
buildValue(69),
|
|
||||||
]), latus)(context)).to.be.true;
|
|
||||||
expect(compile(buildCondition('contains', [
|
|
||||||
buildValue([1, 2, 3]),
|
|
||||||
buildValue(69),
|
|
||||||
]), latus)(context)).to.be.false;
|
|
||||||
expect(compile(buildCondition('contains', [
|
|
||||||
buildValue(69),
|
|
||||||
buildValue(69),
|
|
||||||
]), latus)(context)).to.be.false;
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,40 +0,0 @@
|
||||||
import {Latus} from '@latus/core';
|
|
||||||
import {expect} from 'chai';
|
|
||||||
|
|
||||||
import {
|
|
||||||
buildInvoke,
|
|
||||||
buildExpression,
|
|
||||||
} from '../src/builders';
|
|
||||||
import Context from '../src/context';
|
|
||||||
import compile from '../src/compilers/compile';
|
|
||||||
|
|
||||||
let latus;
|
|
||||||
let context;
|
|
||||||
beforeEach(async () => {
|
|
||||||
latus = Latus.mock({
|
|
||||||
'@avocado/behavior': require('../src'),
|
|
||||||
});
|
|
||||||
await Promise.all(latus.invokeFlat('@latus/core/starting'));
|
|
||||||
context = new Context({}, latus);
|
|
||||||
});
|
|
||||||
describe('Expression', () => {
|
|
||||||
it('evaluates value expressions', async () => {
|
|
||||||
const test = {foo: 69};
|
|
||||||
context.add('test', test);
|
|
||||||
const expression = compile(buildExpression(['test', 'foo']), latus);
|
|
||||||
expect(expression(context)).to.equal(test.foo);
|
|
||||||
});
|
|
||||||
it('evaluates assignment expressions', async () => {
|
|
||||||
const test = {foo: 69};
|
|
||||||
context.add('test', test);
|
|
||||||
const expression = compile(buildExpression(['test', 'foo'], 420), latus);
|
|
||||||
expression(context);
|
|
||||||
expect(test.foo).to.equal(420);
|
|
||||||
});
|
|
||||||
it('invokes', async () => {
|
|
||||||
const test = {foo: () => 69};
|
|
||||||
context.add('test', test);
|
|
||||||
const expression = compile(buildInvoke(['test', 'foo']), latus);
|
|
||||||
expect(expression(context)).to.equal(69);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,86 +0,0 @@
|
||||||
import {Latus} from '@latus/core';
|
|
||||||
import {expect} from 'chai';
|
|
||||||
|
|
||||||
import {
|
|
||||||
buildCondition,
|
|
||||||
buildExpression,
|
|
||||||
buildExpressions,
|
|
||||||
buildInvoke,
|
|
||||||
buildValue,
|
|
||||||
} from '../src/builders';
|
|
||||||
import Context from '../src/context';
|
|
||||||
import compile from '../src/compilers/compile';
|
|
||||||
|
|
||||||
let latus;
|
|
||||||
let context;
|
|
||||||
beforeEach(async () => {
|
|
||||||
latus = Latus.mock({
|
|
||||||
'@avocado/behavior': require('../src'),
|
|
||||||
});
|
|
||||||
await Promise.all(latus.invokeFlat('@latus/core/starting'));
|
|
||||||
context = new Context({}, latus);
|
|
||||||
});
|
|
||||||
describe('Flow', () => {
|
|
||||||
it('does conditional', async () => {
|
|
||||||
const test = {foo: 69};
|
|
||||||
context.add('test', test);
|
|
||||||
expect(compile(buildExpression(['test', 'foo']), latus)(context)).to.equal(69);
|
|
||||||
const tickingPromise = compile(buildInvoke(
|
|
||||||
['Flow', 'conditional'],
|
|
||||||
[
|
|
||||||
false,
|
|
||||||
compile(buildExpressions([
|
|
||||||
buildExpression(['test', 'foo'], 420),
|
|
||||||
]), latus),
|
|
||||||
],
|
|
||||||
), latus)(context);
|
|
||||||
tickingPromise && tickingPromise.tick();
|
|
||||||
expect(compile(buildExpression(['test', 'foo']), latus)(context)).to.equal(69);
|
|
||||||
const tickingPromise2 = compile(buildInvoke(
|
|
||||||
['Flow', 'conditional'],
|
|
||||||
[
|
|
||||||
true,
|
|
||||||
compile(buildExpressions([
|
|
||||||
buildExpression(['test', 'foo'], 420),
|
|
||||||
]), latus),
|
|
||||||
],
|
|
||||||
), latus)(context);
|
|
||||||
tickingPromise2 && tickingPromise2.tick();
|
|
||||||
expect(compile(buildExpression(['test', 'foo']), latus)(context)).to.equal(420);
|
|
||||||
});
|
|
||||||
it('does nop', async () => {
|
|
||||||
compile(buildInvoke(['Flow', 'nop']), latus)(context);
|
|
||||||
});
|
|
||||||
it('does parallel actions', async () => {
|
|
||||||
const tickingPromise = compile(buildInvoke(
|
|
||||||
['Flow', 'parallel'],
|
|
||||||
[
|
|
||||||
buildExpressions([
|
|
||||||
buildInvoke(['Timing', 'wait'], [100]),
|
|
||||||
buildInvoke(['Timing', 'wait'], [200]),
|
|
||||||
]),
|
|
||||||
],
|
|
||||||
), latus)(context);
|
|
||||||
tickingPromise.tick(200);
|
|
||||||
return tickingPromise;
|
|
||||||
});
|
|
||||||
it('does serial actions', async () => {
|
|
||||||
const DELAY = 30;
|
|
||||||
const tickingPromise = compile(buildInvoke(
|
|
||||||
['Flow', 'serial'],
|
|
||||||
[
|
|
||||||
buildExpressions([
|
|
||||||
buildInvoke(['Timing', 'wait'], [100]),
|
|
||||||
buildInvoke(['Timing', 'wait'], [200]),
|
|
||||||
]),
|
|
||||||
],
|
|
||||||
), latus)(context);
|
|
||||||
tickingPromise.tick(200);
|
|
||||||
let start = Date.now();
|
|
||||||
await Promise.race([
|
|
||||||
new Promise((resolve) => setTimeout(resolve, DELAY)),
|
|
||||||
tickingPromise,
|
|
||||||
])
|
|
||||||
expect(Date.now() - start).to.be.at.least(DELAY * 0.9);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,23 +0,0 @@
|
||||||
import {Latus} from '@latus/core';
|
|
||||||
import {expect} from 'chai';
|
|
||||||
|
|
||||||
import {
|
|
||||||
buildValue,
|
|
||||||
} from '../src/builders';
|
|
||||||
import Context from '../src/context';
|
|
||||||
import compile from '../src/compilers/compile';
|
|
||||||
|
|
||||||
let latus;
|
|
||||||
let context;
|
|
||||||
beforeEach(async () => {
|
|
||||||
latus = Latus.mock({
|
|
||||||
'@avocado/behavior': require('../src'),
|
|
||||||
});
|
|
||||||
await Promise.all(latus.invokeFlat('@latus/core/starting'));
|
|
||||||
context = new Context({}, latus);
|
|
||||||
});
|
|
||||||
describe('Literal', () => {
|
|
||||||
it('evaluates literals', async () => {
|
|
||||||
expect(compile(buildValue(420), latus)(context)).to.equal(420);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,39 +0,0 @@
|
||||||
import {Latus} from '@latus/core';
|
|
||||||
import {expect} from 'chai';
|
|
||||||
|
|
||||||
import {
|
|
||||||
buildInvoke,
|
|
||||||
buildValue,
|
|
||||||
} from '../src/builders';
|
|
||||||
import Context from '../src/context';
|
|
||||||
import compile from '../src/compilers/compile';
|
|
||||||
|
|
||||||
let latus;
|
|
||||||
let context;
|
|
||||||
beforeEach(async () => {
|
|
||||||
latus = Latus.mock({
|
|
||||||
'@avocado/behavior': require('../src'),
|
|
||||||
});
|
|
||||||
await Promise.all(latus.invokeFlat('@latus/core/starting'));
|
|
||||||
context = new Context({}, latus);
|
|
||||||
});
|
|
||||||
describe('Timing', () => {
|
|
||||||
it('waits', async () => {
|
|
||||||
const DELAY = 30;
|
|
||||||
const tickingPromise = compile(buildInvoke(
|
|
||||||
['Timing', 'wait'],
|
|
||||||
[
|
|
||||||
buildValue(300),
|
|
||||||
],
|
|
||||||
), latus)(context);
|
|
||||||
tickingPromise.tick(200);
|
|
||||||
let start = Date.now();
|
|
||||||
await Promise.race([
|
|
||||||
new Promise((resolve) => setTimeout(resolve, DELAY)),
|
|
||||||
tickingPromise,
|
|
||||||
])
|
|
||||||
expect(Date.now() - start).to.be.at.least(DELAY * 0.9);
|
|
||||||
tickingPromise.tick(100);
|
|
||||||
return tickingPromise;
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,51 +0,0 @@
|
||||||
import {Latus} from '@latus/core';
|
|
||||||
import {expect} from 'chai';
|
|
||||||
|
|
||||||
import {
|
|
||||||
buildInvoke,
|
|
||||||
buildValue,
|
|
||||||
} from '../src/builders';
|
|
||||||
import Context from '../src/context';
|
|
||||||
import compile from '../src/compilers/compile';
|
|
||||||
|
|
||||||
let latus;
|
|
||||||
let context;
|
|
||||||
beforeEach(async () => {
|
|
||||||
latus = Latus.mock({
|
|
||||||
'@avocado/behavior': require('../src'),
|
|
||||||
});
|
|
||||||
await Promise.all(latus.invokeFlat('@latus/core/starting'));
|
|
||||||
context = new Context({}, latus);
|
|
||||||
});
|
|
||||||
describe('Utility', () => {
|
|
||||||
it('makes arrays', async () => {
|
|
||||||
expect(compile(buildInvoke(
|
|
||||||
['Utility', 'makeArray'],
|
|
||||||
[
|
|
||||||
buildValue(1),
|
|
||||||
buildValue(2),
|
|
||||||
buildValue(3),
|
|
||||||
],
|
|
||||||
), latus)(context)).to.deep.equal([1, 2, 3]);
|
|
||||||
});
|
|
||||||
it('makes objects', async () => {
|
|
||||||
expect(compile(buildInvoke(
|
|
||||||
['Utility', 'makeObject'],
|
|
||||||
[
|
|
||||||
buildValue('foo'),
|
|
||||||
buildValue(2),
|
|
||||||
buildValue('bar'),
|
|
||||||
buildValue(3),
|
|
||||||
],
|
|
||||||
), latus)(context)).to.deep.equal({foo: 2, bar: 3});
|
|
||||||
});
|
|
||||||
it('merges', async () => {
|
|
||||||
expect(compile(buildInvoke(
|
|
||||||
['Utility', 'merge'],
|
|
||||||
[
|
|
||||||
buildValue({foo: 69, bar: 420}),
|
|
||||||
buildValue({foo: 311, baz: 100}),
|
|
||||||
],
|
|
||||||
), latus)(context)).to.deep.equal({foo: 311, bar: 420, baz: 100});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -163,6 +163,13 @@
|
||||||
debug "4.3.1"
|
debug "4.3.1"
|
||||||
msgpack-lite "^0.1.26"
|
msgpack-lite "^0.1.26"
|
||||||
|
|
||||||
|
"@avocado/sandbox@^1.0.0":
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "http://npm.cha0sdev/@avocado%2fsandbox/-/sandbox-1.0.0.tgz#787158bbaddbbf787b79811c508783acae9dfece"
|
||||||
|
integrity sha512-ZdNK9OBqomL05t10hGHEqJwrOwAj23KWV3npEBgPmfC/j1gePBqqizzz3T2Nzsn67j7ZKe8HG+nhS1y11ZScAA==
|
||||||
|
dependencies:
|
||||||
|
"@babel/types" "^7.13.14"
|
||||||
|
|
||||||
"@avocado/timing@2.0.0", "@avocado/timing@^2.0.0":
|
"@avocado/timing@2.0.0", "@avocado/timing@^2.0.0":
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "http://npm.cha0sdev/@avocado%2ftiming/-/timing-2.0.0.tgz#b84a09f8b50b31d79dc27dd65a3003da810b7f86"
|
resolved "http://npm.cha0sdev/@avocado%2ftiming/-/timing-2.0.0.tgz#b84a09f8b50b31d79dc27dd65a3003da810b7f86"
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import {decorateWithLatus} from '@latus/core';
|
|
||||||
import {gatherComponents} from '@latus/react';
|
import {gatherComponents} from '@latus/react';
|
||||||
|
|
||||||
import EntityController from './controllers/entity';
|
import EntityController from './controllers/entity';
|
||||||
|
@ -12,9 +11,6 @@ export default {
|
||||||
'@avocado/resource/persea.controllers': () => [
|
'@avocado/resource/persea.controllers': () => [
|
||||||
EntityController,
|
EntityController,
|
||||||
],
|
],
|
||||||
'@avocado/resource/resources.decorate': decorateWithLatus(
|
|
||||||
require.context('./resources/decorators', false, /\.js$/),
|
|
||||||
),
|
|
||||||
'@avocado/traits/components': gatherComponents(
|
'@avocado/traits/components': gatherComponents(
|
||||||
require.context('./traits', false, /\.jsx$/),
|
require.context('./traits', false, /\.jsx$/),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
import {Context} from '@avocado/behavior';
|
|
||||||
|
|
||||||
export default (Entity, latus) => class ContextedEntity extends Entity {
|
|
||||||
|
|
||||||
createContext(variables = {}) {
|
|
||||||
const context = this.context
|
|
||||||
? this.context.clone()
|
|
||||||
: new Context(
|
|
||||||
{
|
|
||||||
entity: this,
|
|
||||||
},
|
|
||||||
latus,
|
|
||||||
);
|
|
||||||
const entries = Object.entries(variables);
|
|
||||||
for (let i = 0; i < entries.length; i++) {
|
|
||||||
const [key, value] = entries[i];
|
|
||||||
context.add(key, value);
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
|
@ -1,47 +1,38 @@
|
||||||
import './alive.scss';
|
import './alive.scss';
|
||||||
import {join} from 'path';
|
import {join} from 'path';
|
||||||
|
|
||||||
import {Number} from '@avocado/persea';
|
import {Code, Number} from '@avocado/persea';
|
||||||
import {Condition, Expressions} from '@avocado/behavior/persea';
|
|
||||||
import {
|
import {
|
||||||
hot,
|
hot,
|
||||||
PropTypes,
|
PropTypes,
|
||||||
React,
|
React,
|
||||||
useEffect,
|
|
||||||
useState,
|
|
||||||
} from '@latus/react';
|
} from '@latus/react';
|
||||||
import {useJsonPatcher} from '@avocado/resource/persea';
|
import {useJsonPatcher} from '@avocado/resource/persea';
|
||||||
|
|
||||||
const Alive = ({
|
const Alive = ({
|
||||||
entity,
|
|
||||||
json,
|
json,
|
||||||
path,
|
path,
|
||||||
}) => {
|
}) => {
|
||||||
const patch = useJsonPatcher();
|
const patch = useJsonPatcher();
|
||||||
const [context, setContext] = useState(entity.createContext());
|
|
||||||
useEffect(() => {
|
|
||||||
setContext(entity.createContext());
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [entity]);
|
|
||||||
return (
|
return (
|
||||||
<div className="alive">
|
<div className="alive">
|
||||||
<div className="label">
|
<div className="label">
|
||||||
<div className="vertical">Death</div>
|
<div className="vertical">Death</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="label">
|
<div className="label">
|
||||||
<div className="vertical">Condition</div>
|
<div className="vertical">Check</div>
|
||||||
<Condition
|
<Code
|
||||||
context={context}
|
code={json.params.deathCheck}
|
||||||
value={json.params.deathCondition}
|
name={join(path, 'params/deathCheck')}
|
||||||
path={join(path, 'params/deathCondition')}
|
onChange={patch.onChange(join(path, 'params/deathCheck'))}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="label">
|
<div className="label">
|
||||||
<div className="vertical">Actions</div>
|
<div className="vertical">Script</div>
|
||||||
<Expressions
|
<Code
|
||||||
context={context}
|
code={json.params.deathScript}
|
||||||
value={json.params.deathActions}
|
name={join(path, 'params/deathScript')}
|
||||||
path={join(path, 'params/deathActions')}
|
onChange={patch.onChange(join(path, 'params/deathScript'))}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -67,15 +58,11 @@ const Alive = ({
|
||||||
Alive.displayName = 'Alive';
|
Alive.displayName = 'Alive';
|
||||||
|
|
||||||
Alive.propTypes = {
|
Alive.propTypes = {
|
||||||
entity: PropTypes.shape({
|
entity: PropTypes.shape({}).isRequired,
|
||||||
createContext: PropTypes.func,
|
|
||||||
}).isRequired,
|
|
||||||
json: PropTypes.shape({
|
json: PropTypes.shape({
|
||||||
params: PropTypes.shape({
|
params: PropTypes.shape({
|
||||||
deathActions: PropTypes.shape({
|
deathCheck: PropTypes.string,
|
||||||
expressions: PropTypes.arrayOf(PropTypes.shape({})),
|
deathScript: PropTypes.string,
|
||||||
}),
|
|
||||||
deathCondition: PropTypes.shape({}),
|
|
||||||
}),
|
}),
|
||||||
state: PropTypes.shape({
|
state: PropTypes.shape({
|
||||||
life: PropTypes.number,
|
life: PropTypes.number,
|
||||||
|
|
|
@ -139,30 +139,30 @@ export default (latus) => {
|
||||||
return this.#quadTree;
|
return this.#quadTree;
|
||||||
}
|
}
|
||||||
|
|
||||||
queryEntities(query, condition, context) {
|
// queryEntities(query, condition, context) {
|
||||||
if (!context) {
|
// if (!context) {
|
||||||
// eslint-disable-next-line no-param-reassign
|
// // eslint-disable-next-line no-param-reassign
|
||||||
context = new Context({}, latus);
|
// context = new Context({}, latus);
|
||||||
}
|
// }
|
||||||
const check = compile(condition, latus);
|
// const check = compile(condition, latus);
|
||||||
const candidates = this.visibleEntities(query, true);
|
// const candidates = this.visibleEntities(query, true);
|
||||||
const fails = [];
|
// const fails = [];
|
||||||
for (let i = 0; i < candidates.length; ++i) {
|
// for (let i = 0; i < candidates.length; ++i) {
|
||||||
const entity = candidates[i];
|
// const entity = candidates[i];
|
||||||
context.add('query', entity);
|
// context.add('query', entity);
|
||||||
if (!check(context)) {
|
// if (!check(context)) {
|
||||||
fails.push(entity);
|
// fails.push(entity);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
for (let i = 0; i < fails.length; ++i) {
|
// for (let i = 0; i < fails.length; ++i) {
|
||||||
candidates.splice(candidates.indexOf(fails[i]), 1);
|
// candidates.splice(candidates.indexOf(fails[i]), 1);
|
||||||
}
|
// }
|
||||||
return candidates;
|
// return candidates;
|
||||||
}
|
// }
|
||||||
|
|
||||||
queryPoint(query, condition, context) {
|
// queryPoint(query, condition, context) {
|
||||||
return this.queryEntities(Rectangle.centerOn(query, [1, 1]), condition, context);
|
// return this.queryEntities(Rectangle.centerOn(query, [1, 1]), condition, context);
|
||||||
}
|
// }
|
||||||
|
|
||||||
removeEntity(entity) {
|
removeEntity(entity) {
|
||||||
const uuid = entity.instanceUuid;
|
const uuid = entity.instanceUuid;
|
||||||
|
|
|
@ -188,6 +188,19 @@ export default (latus) => {
|
||||||
this.#markedAsDirty = false;
|
this.#markedAsDirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: behavior decorator
|
||||||
|
get contextOrDefault() {
|
||||||
|
return this.context || {entity: this};
|
||||||
|
}
|
||||||
|
|
||||||
|
createContext(locals = {}) {
|
||||||
|
const {Script} = latus.get('%resources');
|
||||||
|
return Script.createContext({
|
||||||
|
...this.contextOrDefault,
|
||||||
|
...locals,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async destroy() {
|
async destroy() {
|
||||||
if (this.#isDestroying) {
|
if (this.#isDestroying) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -1,12 +1,3 @@
|
||||||
import {
|
|
||||||
Actions,
|
|
||||||
buildCondition,
|
|
||||||
buildInvoke,
|
|
||||||
buildExpression,
|
|
||||||
buildValue,
|
|
||||||
compile,
|
|
||||||
Context,
|
|
||||||
} from '@avocado/behavior';
|
|
||||||
import {TickingPromise} from '@avocado/core';
|
import {TickingPromise} from '@avocado/core';
|
||||||
import {StateProperty, Trait} from '@avocado/traits';
|
import {StateProperty, Trait} from '@avocado/traits';
|
||||||
import {compose} from '@latus/core';
|
import {compose} from '@latus/core';
|
||||||
|
@ -22,11 +13,7 @@ const decorate = compose(
|
||||||
|
|
||||||
export default (latus) => class Alive extends decorate(Trait) {
|
export default (latus) => class Alive extends decorate(Trait) {
|
||||||
|
|
||||||
#context = new Context({}, latus);
|
#deathCheck;
|
||||||
|
|
||||||
#deathActions;
|
|
||||||
|
|
||||||
#deathCondition;
|
|
||||||
|
|
||||||
#hasDied = false;
|
#hasDied = false;
|
||||||
|
|
||||||
|
@ -34,13 +21,6 @@ export default (latus) => class Alive extends decorate(Trait) {
|
||||||
|
|
||||||
#packets = [];
|
#packets = [];
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
const {deathActions, deathCondition} = this.constructor.defaultParams();
|
|
||||||
this.#deathActions = deathActions;
|
|
||||||
this.#deathCondition = deathCondition;
|
|
||||||
}
|
|
||||||
|
|
||||||
acceptPacket(packet) {
|
acceptPacket(packet) {
|
||||||
switch (packet.constructor.type) {
|
switch (packet.constructor.type) {
|
||||||
case 'Died':
|
case 'Died':
|
||||||
|
@ -60,30 +40,23 @@ export default (latus) => class Alive extends decorate(Trait) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static defaultParams() {
|
static defaultParams() {
|
||||||
const playDeathSound = buildInvoke(['entity', 'playSound'], [
|
|
||||||
buildValue('deathSound'),
|
|
||||||
]);
|
|
||||||
const squeeze = buildInvoke(['entity', 'transition'], [
|
|
||||||
{
|
|
||||||
opacity: 0,
|
|
||||||
visibleScaleX: 0.3,
|
|
||||||
visibleScaleY: 3,
|
|
||||||
},
|
|
||||||
0.4,
|
|
||||||
]);
|
|
||||||
const isLifeGone = buildCondition('<=', [
|
|
||||||
buildExpression(['entity', 'life']),
|
|
||||||
0,
|
|
||||||
]);
|
|
||||||
return {
|
return {
|
||||||
deathActions: {
|
deathCheck: 'return entity.life <= 0',
|
||||||
type: 'expressions',
|
deathScript: `
|
||||||
expressions: [
|
if ('client' === SIDE) {
|
||||||
playDeathSound,
|
TickingPromise.all([
|
||||||
squeeze,
|
entity.playSound("deathSound"),
|
||||||
],
|
entity.transition(
|
||||||
},
|
{
|
||||||
deathCondition: isLifeGone,
|
opacity: 0,
|
||||||
|
visibleScaleX: 0.3,
|
||||||
|
visibleScaleY: 3,
|
||||||
|
},
|
||||||
|
0.4,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,27 +67,22 @@ export default (latus) => class Alive extends decorate(Trait) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static children() {
|
async die() {
|
||||||
return {
|
this.entity.emit('startedDying');
|
||||||
die: {
|
const {Script} = latus.get('%resources');
|
||||||
type: 'void',
|
const deathScript = await Script.load(this.params.deathScript, this.entity.contextOrDefault);
|
||||||
label: 'Force death',
|
await this.entity.addTickingPromise(deathScript.tickingPromise());
|
||||||
args: [],
|
const died = this.entity.invokeHookFlat('died');
|
||||||
|
await this.entity.addTickingPromise(new TickingPromise(
|
||||||
|
() => {},
|
||||||
|
(elapsed, resolve) => {
|
||||||
|
if (died.every((die) => die(elapsed), (r) => !!r)) {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
life: {
|
));
|
||||||
type: 'number',
|
this.#hasDied = true;
|
||||||
label: 'Life points',
|
this.entity.destroy();
|
||||||
},
|
|
||||||
maxLife: {
|
|
||||||
type: 'number',
|
|
||||||
label: 'Maximum life points',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy() {
|
|
||||||
super.destroy();
|
|
||||||
this.#context.destroy();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hooks() {
|
hooks() {
|
||||||
|
@ -150,14 +118,8 @@ export default (latus) => class Alive extends decorate(Trait) {
|
||||||
|
|
||||||
async load(json) {
|
async load(json) {
|
||||||
await super.load(json);
|
await super.load(json);
|
||||||
this.#context = new Context(
|
const {Script} = latus.get('%resources');
|
||||||
{
|
this.#deathCheck = await Script.load(this.params.deathCheck, this.entity.contextOrDefault);
|
||||||
entity: this.entity,
|
|
||||||
},
|
|
||||||
latus,
|
|
||||||
);
|
|
||||||
this.#deathActions = new Actions(compile(this.params.deathActions, latus));
|
|
||||||
this.#deathCondition = compile(this.params.deathCondition, latus);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get maxLife() {
|
get maxLife() {
|
||||||
|
@ -172,24 +134,11 @@ export default (latus) => class Alive extends decorate(Trait) {
|
||||||
methods() {
|
methods() {
|
||||||
return {
|
return {
|
||||||
|
|
||||||
die: async () => {
|
die: () => {
|
||||||
if (this.#isDying) {
|
if (!this.#isDying) {
|
||||||
return;
|
this.#isDying = this.die();
|
||||||
}
|
}
|
||||||
this.#isDying = true;
|
return this.#isDying;
|
||||||
this.entity.emit('startedDying');
|
|
||||||
await this.entity.addTickingPromise(this.#deathActions.tickingPromise(this.#context));
|
|
||||||
const died = this.entity.invokeHookFlat('died');
|
|
||||||
await this.entity.addTickingPromise(new TickingPromise(
|
|
||||||
() => {},
|
|
||||||
(elapsed, resolve) => {
|
|
||||||
if (died.every((die) => die(elapsed), (r) => !!r)) {
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
));
|
|
||||||
this.#hasDied = true;
|
|
||||||
this.entity.destroy();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -212,8 +161,12 @@ export default (latus) => class Alive extends decorate(Trait) {
|
||||||
|
|
||||||
tick() {
|
tick() {
|
||||||
if ('client' !== process.env.SIDE) {
|
if ('client' !== process.env.SIDE) {
|
||||||
if (!this.#isDying && this.#deathCondition(this.#context)) {
|
if (!this.#isDying) {
|
||||||
this.entity.die();
|
this.#deathCheck.evaluate(({value: died}) => {
|
||||||
|
if (died) {
|
||||||
|
this.entity.die();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import {Context} from '@avocado/behavior';
|
|
||||||
import {compose} from '@latus/core';
|
import {compose} from '@latus/core';
|
||||||
import {StateProperty, Trait} from '@avocado/traits';
|
import {StateProperty, Trait} from '@avocado/traits';
|
||||||
import merge from 'deepmerge';
|
import merge from 'deepmerge';
|
||||||
|
@ -203,13 +202,7 @@ export default (latus) => class Spawner extends decorate(Trait) {
|
||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
},
|
},
|
||||||
|
|
||||||
spawn: async (key, json = {}, context) => {
|
spawn: async (key, json = {}) => {
|
||||||
if (!context && json instanceof Context) {
|
|
||||||
/* eslint-disable no-param-reassign */
|
|
||||||
context = json;
|
|
||||||
json = {};
|
|
||||||
/* eslint-enable no-param-reassign */
|
|
||||||
}
|
|
||||||
if (!this.maySpawn()) {
|
if (!this.maySpawn()) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
@ -228,13 +221,7 @@ export default (latus) => class Spawner extends decorate(Trait) {
|
||||||
return entity;
|
return entity;
|
||||||
},
|
},
|
||||||
|
|
||||||
spawnAt: async (key, position, json = {}, context) => {
|
spawnAt: async (key, position, json = {}) => {
|
||||||
if (!context && json instanceof Context) {
|
|
||||||
/* eslint-disable no-param-reassign */
|
|
||||||
context = json;
|
|
||||||
json = {};
|
|
||||||
/* eslint-enable no-param-reassign */
|
|
||||||
}
|
|
||||||
const entity = this.maySpawn()
|
const entity = this.maySpawn()
|
||||||
? await this.entity.spawn(
|
? await this.entity.spawn(
|
||||||
key,
|
key,
|
||||||
|
@ -245,7 +232,6 @@ export default (latus) => class Spawner extends decorate(Trait) {
|
||||||
arrayMerge: (l, r) => r,
|
arrayMerge: (l, r) => r,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
context,
|
|
||||||
)
|
)
|
||||||
: undefined;
|
: undefined;
|
||||||
if (entity) {
|
if (entity) {
|
||||||
|
|
|
@ -229,7 +229,7 @@ export default () => class Visible extends decorate(Trait) {
|
||||||
}
|
}
|
||||||
|
|
||||||
set visibleScaleX(x) {
|
set visibleScaleX(x) {
|
||||||
this.state.visibleScale = [x, this.state.visibleScale[1]];
|
this.entity.visibleScale = [x, this.state.visibleScale[1]];
|
||||||
}
|
}
|
||||||
|
|
||||||
get visibleScaleY() {
|
get visibleScaleY() {
|
||||||
|
@ -237,7 +237,7 @@ export default () => class Visible extends decorate(Trait) {
|
||||||
}
|
}
|
||||||
|
|
||||||
set visibleScaleY(y) {
|
set visibleScaleY(y) {
|
||||||
this.state.visibleScale = [this.state.visibleScale[0], y];
|
this.entity.visibleScale = [this.state.visibleScale[0], y];
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
/* eslint-disable no-unused-vars */
|
/* eslint-disable no-unused-vars */
|
||||||
import {buildInvoke} from '@avocado/behavior';
|
|
||||||
import {Rectangle, Vector} from '@avocado/math';
|
import {Rectangle, Vector} from '@avocado/math';
|
||||||
import {LfoResult, Ticker} from '@avocado/timing';
|
import {LfoResult, Ticker} from '@avocado/timing';
|
||||||
import {Trait} from '@avocado/traits';
|
import {Trait} from '@avocado/traits';
|
||||||
|
@ -65,10 +64,14 @@ export default () => class Initiator extends Trait {
|
||||||
Rectangle.centerOn(incident, [16, 16]),
|
Rectangle.centerOn(incident, [16, 16]),
|
||||||
Vector.scale(Vector.abs(directional), 2),
|
Vector.scale(Vector.abs(directional), 2),
|
||||||
);
|
);
|
||||||
const interactives = this.entity.list.queryEntities(
|
const {list} = this.entity;
|
||||||
query,
|
const entities = list.visibleEntities(query);
|
||||||
buildInvoke(['query', 'is'], ['Interactive']),
|
const interactives = [];
|
||||||
);
|
for (let i = 0; i < entities.length; ++i) {
|
||||||
|
if (entities[i].is('Interactive')) {
|
||||||
|
interactives.push(entities[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
const oldTarget = this.#target;
|
const oldTarget = this.#target;
|
||||||
if (interactives.length > 0) {
|
if (interactives.length > 0) {
|
||||||
[this.#target] = interactives
|
[this.#target] = interactives
|
||||||
|
|
|
@ -1,9 +1,3 @@
|
||||||
import {
|
|
||||||
Actions,
|
|
||||||
buildExpressions,
|
|
||||||
compile,
|
|
||||||
Context,
|
|
||||||
} from '@avocado/behavior';
|
|
||||||
import {StateProperty, Trait} from '@avocado/traits';
|
import {StateProperty, Trait} from '@avocado/traits';
|
||||||
import {compose} from '@latus/core';
|
import {compose} from '@latus/core';
|
||||||
|
|
||||||
|
@ -23,7 +17,7 @@ export default (latus) => class Interactive extends decorate(Trait) {
|
||||||
|
|
||||||
static defaultParams() {
|
static defaultParams() {
|
||||||
return {
|
return {
|
||||||
actions: buildExpressions([]),
|
interactScript: 'void 0',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,23 +27,16 @@ export default (latus) => class Interactive extends decorate(Trait) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async load(json) {
|
|
||||||
await super.load(json);
|
|
||||||
this.#actions = new Actions(compile(this.params.actions, latus));
|
|
||||||
}
|
|
||||||
|
|
||||||
methods() {
|
methods() {
|
||||||
return {
|
return {
|
||||||
|
|
||||||
interact: (initiator) => {
|
interact: async (initiator) => {
|
||||||
const context = new Context(
|
const {Script} = latus.get('%resources');
|
||||||
{
|
const script = await Script.load(this.params.interactScript, {
|
||||||
entity: this.entity,
|
entity: this.entity,
|
||||||
initiator,
|
initiator,
|
||||||
},
|
});
|
||||||
latus,
|
this.entity.addTickingPromise(script.tickingPromise());
|
||||||
);
|
|
||||||
this.entity.addTickingPromise(this.#actions.serial(context));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
7
packages/persea/.latusrc.js
Normal file
7
packages/persea/.latusrc.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
module.exports = () => ({
|
||||||
|
dependencies: {
|
||||||
|
dom: [
|
||||||
|
'ace-builds',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
|
@ -16,9 +16,9 @@ const Code = ({
|
||||||
onChange,
|
onChange,
|
||||||
}) => (
|
}) => (
|
||||||
<AceEditor
|
<AceEditor
|
||||||
name={name}
|
name={name.replace(/[^a-zA-Z0-9]/g, '-')}
|
||||||
fontSize={fontSize}
|
fontSize={fontSize}
|
||||||
height={height}
|
height={height || `${code.split('\n').length}em`}
|
||||||
width="100%"
|
width="100%"
|
||||||
mode="javascript"
|
mode="javascript"
|
||||||
tabSize={2}
|
tabSize={2}
|
||||||
|
@ -30,7 +30,7 @@ const Code = ({
|
||||||
|
|
||||||
Code.defaultProps = {
|
Code.defaultProps = {
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
height: '10em',
|
height: null,
|
||||||
onChange: () => {},
|
onChange: () => {},
|
||||||
name: 'ace-editor',
|
name: 'ace-editor',
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,35 +1,17 @@
|
||||||
import {join} from 'path';
|
import {join} from 'path';
|
||||||
|
|
||||||
import {Expressions} from '@avocado/behavior/persea';
|
import {Code, Json} from '@avocado/persea';
|
||||||
import {fullEntity} from '@avocado/entity';
|
|
||||||
import {Json} from '@avocado/persea';
|
|
||||||
import {useJsonPatcher} from '@avocado/resource/persea';
|
import {useJsonPatcher} from '@avocado/resource/persea';
|
||||||
import {
|
import {
|
||||||
PropTypes,
|
PropTypes,
|
||||||
React,
|
React,
|
||||||
useEffect,
|
|
||||||
useLatus,
|
|
||||||
useState,
|
|
||||||
} from '@latus/react';
|
} from '@latus/react';
|
||||||
|
|
||||||
const Collider = ({
|
const Collider = ({
|
||||||
entity,
|
|
||||||
json,
|
json,
|
||||||
path,
|
path,
|
||||||
}) => {
|
}) => {
|
||||||
const latus = useLatus();
|
|
||||||
const patch = useJsonPatcher();
|
const patch = useJsonPatcher();
|
||||||
const [context, setContext] = useState(() => entity.createContext());
|
|
||||||
useEffect(() => {
|
|
||||||
const createContext = async () => {
|
|
||||||
setContext(entity.createContext({
|
|
||||||
incident: [0, 0],
|
|
||||||
other: await fullEntity(latus),
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
createContext();
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [entity]);
|
|
||||||
return (
|
return (
|
||||||
<div className="collider">
|
<div className="collider">
|
||||||
<div className="label">
|
<div className="label">
|
||||||
|
@ -44,23 +26,19 @@ const Collider = ({
|
||||||
<div>
|
<div>
|
||||||
<div className="label">
|
<div className="label">
|
||||||
<div className="vertical">Start</div>
|
<div className="vertical">Start</div>
|
||||||
{context && (
|
<Code
|
||||||
<Expressions
|
code={json.params.collisionStartScript}
|
||||||
context={context}
|
name={join(path, 'params/collisionStartScript')}
|
||||||
value={json.params.collisionStartActions}
|
onChange={patch.onChange(join(path, 'params/collisionStartScript'))}
|
||||||
path={join(path, 'params/collisionStartActions')}
|
/>
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="label">
|
<div className="label">
|
||||||
<div className="vertical">End</div>
|
<div className="vertical">End</div>
|
||||||
{context && (
|
<Code
|
||||||
<Expressions
|
code={json.params.collisionEndScript}
|
||||||
context={context}
|
name={join(path, 'params/collisionEndScript')}
|
||||||
value={json.params.collisionEndActions}
|
onChange={patch.onChange(join(path, 'params/collisionEndScript'))}
|
||||||
path={join(path, 'params/collisionEndActions')}
|
/>
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -117,17 +95,15 @@ Collider.defaultProps = {};
|
||||||
Collider.displayName = 'Collider';
|
Collider.displayName = 'Collider';
|
||||||
|
|
||||||
Collider.propTypes = {
|
Collider.propTypes = {
|
||||||
entity: PropTypes.shape({
|
entity: PropTypes.shape({}).isRequired,
|
||||||
createContext: PropTypes.func,
|
|
||||||
}).isRequired,
|
|
||||||
json: PropTypes.shape({
|
json: PropTypes.shape({
|
||||||
params: PropTypes.shape({
|
params: PropTypes.shape({
|
||||||
activeCollision: PropTypes.bool,
|
activeCollision: PropTypes.bool,
|
||||||
collidesWithGroups: PropTypes.arrayOf(
|
collidesWithGroups: PropTypes.arrayOf(
|
||||||
PropTypes.string,
|
PropTypes.string,
|
||||||
),
|
),
|
||||||
collisionEndActions: PropTypes.shape({}),
|
collisionEndScript: PropTypes.string,
|
||||||
collisionStartActions: PropTypes.shape({}),
|
collisionStartScript: PropTypes.string,
|
||||||
collisionGroup: PropTypes.string,
|
collisionGroup: PropTypes.string,
|
||||||
isSensor: PropTypes.bool,
|
isSensor: PropTypes.bool,
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import {Actions, compile, Context} from '@avocado/behavior';
|
|
||||||
import {Rectangle, Vector} from '@avocado/math';
|
import {Rectangle, Vector} from '@avocado/math';
|
||||||
import {StateProperty, Trait} from '@avocado/traits';
|
import {StateProperty, Trait} from '@avocado/traits';
|
||||||
import {compose} from '@latus/core';
|
import {compose} from '@latus/core';
|
||||||
|
@ -12,10 +11,6 @@ export default (latus) => class Collider extends decorate(Trait) {
|
||||||
|
|
||||||
#collidesWithGroups = [];
|
#collidesWithGroups = [];
|
||||||
|
|
||||||
#collisionEndActions;
|
|
||||||
|
|
||||||
#collisionStartActions;
|
|
||||||
|
|
||||||
#collisionGroup = '';
|
#collisionGroup = '';
|
||||||
|
|
||||||
#doesNotCollideWith = [];
|
#doesNotCollideWith = [];
|
||||||
|
@ -27,8 +22,6 @@ export default (latus) => class Collider extends decorate(Trait) {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
({
|
({
|
||||||
collisionEndActions: this.#collisionEndActions,
|
|
||||||
collisionStartActions: this.#collisionStartActions,
|
|
||||||
collidesWithGroups: this.#collidesWithGroups,
|
collidesWithGroups: this.#collidesWithGroups,
|
||||||
collisionGroup: this.#collisionGroup,
|
collisionGroup: this.#collisionGroup,
|
||||||
isSensor: this.#isSensor,
|
isSensor: this.#isSensor,
|
||||||
|
@ -41,14 +34,8 @@ export default (latus) => class Collider extends decorate(Trait) {
|
||||||
collidesWithGroups: [
|
collidesWithGroups: [
|
||||||
'default',
|
'default',
|
||||||
],
|
],
|
||||||
collisionEndActions: {
|
collisionStartScript: 'void 0',
|
||||||
type: 'expressions',
|
collisionEndScript: 'void 0',
|
||||||
expressions: [],
|
|
||||||
},
|
|
||||||
collisionStartActions: {
|
|
||||||
type: 'expressions',
|
|
||||||
expressions: [],
|
|
||||||
},
|
|
||||||
collisionGroup: 'default',
|
collisionGroup: 'default',
|
||||||
isSensor: false,
|
isSensor: false,
|
||||||
};
|
};
|
||||||
|
@ -68,63 +55,6 @@ export default (latus) => class Collider extends decorate(Trait) {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
static children() {
|
|
||||||
return {
|
|
||||||
collidesWith: {
|
|
||||||
advanced: true,
|
|
||||||
type: 'bool',
|
|
||||||
label: 'I collide with $1.',
|
|
||||||
args: [
|
|
||||||
{
|
|
||||||
type: 'entity',
|
|
||||||
label: 'other',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
doesNotCollideWith: {
|
|
||||||
advanced: true,
|
|
||||||
type: 'bool',
|
|
||||||
label: 'I would not collide with $1.',
|
|
||||||
args: [
|
|
||||||
{
|
|
||||||
type: 'entity',
|
|
||||||
label: 'other',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
isCheckingCollisions: {
|
|
||||||
type: 'bool',
|
|
||||||
label: 'Is checking collisions',
|
|
||||||
},
|
|
||||||
isColliding: {
|
|
||||||
type: 'bool',
|
|
||||||
label: 'Is able to collide',
|
|
||||||
},
|
|
||||||
setDoesCollideWith: {
|
|
||||||
advanced: true,
|
|
||||||
type: 'void',
|
|
||||||
label: 'Set $1 as colliding with myself.',
|
|
||||||
args: [
|
|
||||||
{
|
|
||||||
type: 'entity',
|
|
||||||
label: 'other',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
setDoesNotCollideWith: {
|
|
||||||
advanced: true,
|
|
||||||
type: 'void',
|
|
||||||
label: 'Set $1 as not colliding with myself.',
|
|
||||||
args: [
|
|
||||||
{
|
|
||||||
type: 'entity',
|
|
||||||
label: 'other',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
super.destroy();
|
super.destroy();
|
||||||
this.releaseAllCollisions();
|
this.releaseAllCollisions();
|
||||||
|
@ -206,32 +136,22 @@ export default (latus) => class Collider extends decorate(Trait) {
|
||||||
await super.load(json);
|
await super.load(json);
|
||||||
const {
|
const {
|
||||||
collidesWithGroups,
|
collidesWithGroups,
|
||||||
collisionEndActions,
|
|
||||||
collisionGroup,
|
collisionGroup,
|
||||||
collisionStartActions,
|
|
||||||
isSensor,
|
isSensor,
|
||||||
} = this.params;
|
} = this.params;
|
||||||
this.#collidesWithGroups = collidesWithGroups;
|
this.#collidesWithGroups = collidesWithGroups;
|
||||||
this.#collisionEndActions = collisionEndActions.expressions.length > 0
|
|
||||||
? new Actions(compile(collisionEndActions, latus))
|
|
||||||
: undefined;
|
|
||||||
this.#collisionStartActions = collisionStartActions.expressions.length > 0
|
|
||||||
? new Actions(compile(collisionStartActions, latus))
|
|
||||||
: undefined;
|
|
||||||
this.#collisionGroup = collisionGroup;
|
this.#collisionGroup = collisionGroup;
|
||||||
this.#isSensor = isSensor;
|
this.#isSensor = isSensor;
|
||||||
}
|
}
|
||||||
|
|
||||||
pushCollisionTickingPromise(actions, other, incident) {
|
async pushCollisionTickingPromise(codeOrUri, other, incident) {
|
||||||
const context = new Context(
|
const {Script} = latus.get('%resources');
|
||||||
{
|
const script = await Script.load(codeOrUri, {
|
||||||
entity: this.entity,
|
entity: this.entity,
|
||||||
incident,
|
incident,
|
||||||
other,
|
other,
|
||||||
},
|
});
|
||||||
latus,
|
this.entity.addTickingPromise(script.tickingPromise());
|
||||||
);
|
|
||||||
this.entity.addTickingPromise(actions.tickingPromise(context));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
releaseAllCollisions() {
|
releaseAllCollisions() {
|
||||||
|
@ -258,8 +178,8 @@ export default (latus) => class Collider extends decorate(Trait) {
|
||||||
const index = this.indexOfCollidingEntity(other);
|
const index = this.indexOfCollidingEntity(other);
|
||||||
if (-1 !== index) {
|
if (-1 !== index) {
|
||||||
this.#isCollidingWith.splice(index, 1);
|
this.#isCollidingWith.splice(index, 1);
|
||||||
if (this.#collisionEndActions) {
|
if (this.params.collisionEndScript) {
|
||||||
this.pushCollisionTickingPromise(this.#collisionEndActions, other, incident);
|
this.pushCollisionTickingPromise(this.params.collisionEndScript, other, incident);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -271,8 +191,8 @@ export default (latus) => class Collider extends decorate(Trait) {
|
||||||
entity: other,
|
entity: other,
|
||||||
incident,
|
incident,
|
||||||
});
|
});
|
||||||
if (this.#collisionStartActions) {
|
if (this.params.collisionStartScript) {
|
||||||
this.pushCollisionTickingPromise(this.#collisionStartActions, other, incident);
|
this.pushCollisionTickingPromise(this.params.collisionStartScript, other, incident);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -10,7 +10,6 @@ import useJsonPatcher from '../../hooks/use-json-patcher';
|
||||||
import {controller as controllerPropTypes} from '../../prop-types';
|
import {controller as controllerPropTypes} from '../../prop-types';
|
||||||
|
|
||||||
const Json = ({path, resource}) => {
|
const Json = ({path, resource}) => {
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
||||||
const patch = useJsonPatcher();
|
const patch = useJsonPatcher();
|
||||||
return (
|
return (
|
||||||
<JsonComponent
|
<JsonComponent
|
||||||
|
|
|
@ -6,8 +6,10 @@ export default class Sandbox {
|
||||||
|
|
||||||
constructor(ast, context) {
|
constructor(ast, context) {
|
||||||
this.ast = ast;
|
this.ast = ast;
|
||||||
this.reset();
|
this.parents = new WeakMap();
|
||||||
|
this.scopes = new WeakMap();
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
this.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
get context() {
|
get context() {
|
||||||
|
@ -53,13 +55,17 @@ export default class Sandbox {
|
||||||
const scope = this.nodeScope(node);
|
const scope = this.nodeScope(node);
|
||||||
const {Vasync, value} = this.evaluate(right);
|
const {Vasync, value} = this.evaluate(right);
|
||||||
if (!types.isMemberExpression(left)) {
|
if (!types.isMemberExpression(left)) {
|
||||||
|
const assign = (value) => {
|
||||||
|
scope.set(left.name, value);
|
||||||
|
return value;
|
||||||
|
};
|
||||||
if (Vasync) {
|
if (Vasync) {
|
||||||
return {
|
return {
|
||||||
async: true,
|
async: true,
|
||||||
value: value.then((value) => scope.set(left.name, value)),
|
value: value.then(assign),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {value: scope.set(left.name, value)};
|
return {value: assign(value)};
|
||||||
}
|
}
|
||||||
const {
|
const {
|
||||||
computed,
|
computed,
|
||||||
|
@ -67,7 +73,7 @@ export default class Sandbox {
|
||||||
optional,
|
optional,
|
||||||
property,
|
property,
|
||||||
} = left;
|
} = left;
|
||||||
const assign = (O, P, value) => {
|
const memberAssign = (O, P, value) => {
|
||||||
if (optional && !O) {
|
if (optional && !O) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
@ -75,7 +81,7 @@ export default class Sandbox {
|
||||||
return O[P] = value;
|
return O[P] = value;
|
||||||
};
|
};
|
||||||
const makeAsync = (O, P, value) => (
|
const makeAsync = (O, P, value) => (
|
||||||
Promise.all([O, P, value]).then(([O, P, value]) => assign(O, P, value))
|
Promise.all([O, P, value]).then(([O, P, value]) => memberAssign(O, P, value))
|
||||||
);
|
);
|
||||||
const O = this.evaluate(object);
|
const O = this.evaluate(object);
|
||||||
const P = computed ? this.evaluate(property) : {value: property.name};
|
const P = computed ? this.evaluate(property) : {value: property.name};
|
||||||
|
@ -86,7 +92,7 @@ export default class Sandbox {
|
||||||
value: makeAsync(O.value, P.value, value),
|
value: makeAsync(O.value, P.value, value),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {value: assign(O.value, P.value, value)};
|
return {value: memberAssign(O.value, P.value, value)};
|
||||||
}
|
}
|
||||||
|
|
||||||
evaluateAwaitExpression({argument}) {
|
evaluateAwaitExpression({argument}) {
|
||||||
|
@ -132,15 +138,17 @@ export default class Sandbox {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const {async: lasync, value: left} = this.evaluate(node.left);
|
const left = this.evaluate(node.left);
|
||||||
const {async: rasync, value: right} = this.evaluate(node.right);
|
const right = this.evaluate(node.right);
|
||||||
if (lasync || rasync) {
|
if (left.async || right.async) {
|
||||||
return {
|
return {
|
||||||
async: true,
|
async: true,
|
||||||
value: Promise.all([left, right]).then(([left, right]) => binary(left, right)),
|
value: Promise
|
||||||
|
.all([left.value, right.value])
|
||||||
|
.then(([left, right]) => binary(left, right)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {value: binary(left, right)};
|
return {value: binary(left.value, right.value)};
|
||||||
}
|
}
|
||||||
|
|
||||||
evaluateCallExpression(node) {
|
evaluateCallExpression(node) {
|
||||||
|
@ -148,6 +156,7 @@ export default class Sandbox {
|
||||||
const args = [];
|
const args = [];
|
||||||
for (let i = 0; i < node.arguments.length; i++) {
|
for (let i = 0; i < node.arguments.length; i++) {
|
||||||
const arg = node.arguments[i];
|
const arg = node.arguments[i];
|
||||||
|
this.setNextScope(arg, this.nodeScope(node));
|
||||||
const {async, value} = this.evaluate(arg);
|
const {async, value} = this.evaluate(arg);
|
||||||
// eslint-disable-next-line no-bitwise
|
// eslint-disable-next-line no-bitwise
|
||||||
asyncArgs |= async;
|
asyncArgs |= async;
|
||||||
|
@ -190,6 +199,7 @@ export default class Sandbox {
|
||||||
optional: memberOptional,
|
optional: memberOptional,
|
||||||
property,
|
property,
|
||||||
} = callee;
|
} = callee;
|
||||||
|
this.setNextScope(callee);
|
||||||
const O = this.evaluate(object);
|
const O = this.evaluate(object);
|
||||||
const P = computed ? this.evaluate(property) : {value: property.name};
|
const P = computed ? this.evaluate(property) : {value: property.name};
|
||||||
if (asyncArgs || O.async || P.async) {
|
if (asyncArgs || O.async || P.async) {
|
||||||
|
@ -209,7 +219,8 @@ export default class Sandbox {
|
||||||
}
|
}
|
||||||
|
|
||||||
evaluateIdentifier(node) {
|
evaluateIdentifier(node) {
|
||||||
return {value: this.nodeScope(node).get(node.name)};
|
const scope = this.nodeScope(node);
|
||||||
|
return {value: scope.get(node.name)};
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this
|
// eslint-disable-next-line class-methods-use-this
|
||||||
|
@ -300,6 +311,37 @@ export default class Sandbox {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
evaluateUnaryExpression(node) {
|
||||||
|
const unary = (arg) => {
|
||||||
|
switch (node.operator) {
|
||||||
|
/* eslint-disable no-multi-spaces, switch-colon-spacing */
|
||||||
|
case '+' : return +arg;
|
||||||
|
case '-' : return -arg;
|
||||||
|
case '!' : return !arg;
|
||||||
|
// eslint-disable-next-line no-bitwise
|
||||||
|
case '~' : return ~arg;
|
||||||
|
case 'typeof': return typeof arg;
|
||||||
|
// eslint-disable-next-line no-void
|
||||||
|
case 'void' : return undefined;
|
||||||
|
// case 'delete': ...
|
||||||
|
case 'throw' : throw arg;
|
||||||
|
/* no-multi-spaces, switch-colon-spacing */
|
||||||
|
default:
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error("evaluateUnaryExpression(): Can't handle operator", node.operator);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const arg = this.evaluate(node.argument);
|
||||||
|
if (arg.async) {
|
||||||
|
return {
|
||||||
|
async: true,
|
||||||
|
value: Promise.resolve(arg.value).then(unary),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {value: unary(arg.value)};
|
||||||
|
}
|
||||||
|
|
||||||
evaluateUpdateExpression(node) {
|
evaluateUpdateExpression(node) {
|
||||||
const {argument, operator, prefix} = node;
|
const {argument, operator, prefix} = node;
|
||||||
const {async, value} = this.evaluate(argument);
|
const {async, value} = this.evaluate(argument);
|
||||||
|
@ -375,8 +417,10 @@ export default class Sandbox {
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
const {context} = this;
|
||||||
this.parents = new WeakMap();
|
this.parents = new WeakMap();
|
||||||
this.scopes = new WeakMap();
|
this.scopes = new WeakMap();
|
||||||
|
this.context = context;
|
||||||
this.runner = (function* traverse() {
|
this.runner = (function* traverse() {
|
||||||
return yield* self.traverse(self.ast);
|
return yield* self.traverse(self.ast);
|
||||||
}());
|
}());
|
||||||
|
@ -414,6 +458,7 @@ export default class Sandbox {
|
||||||
'Literal',
|
'Literal',
|
||||||
'MemberExpression',
|
'MemberExpression',
|
||||||
'ObjectExpression',
|
'ObjectExpression',
|
||||||
|
'UnaryExpression',
|
||||||
'UpdateExpression',
|
'UpdateExpression',
|
||||||
];
|
];
|
||||||
for (let i = 0; i < flat.length; i++) {
|
for (let i = 0; i < flat.length; i++) {
|
||||||
|
@ -615,7 +660,9 @@ export default class Sandbox {
|
||||||
// Evaluate...
|
// Evaluate...
|
||||||
if (types.isReturnStatement(node)) {
|
if (types.isReturnStatement(node)) {
|
||||||
// eslint-disable-next-line consistent-return
|
// eslint-disable-next-line consistent-return
|
||||||
return this.evaluate(node.argument);
|
return !node.argument
|
||||||
|
? {value: undefined}
|
||||||
|
: this.evaluate(node.argument);
|
||||||
}
|
}
|
||||||
if (types.isDirective(node)) {
|
if (types.isDirective(node)) {
|
||||||
yield this.evaluate(node.value);
|
yield this.evaluate(node.value);
|
||||||
|
|
|
@ -55,11 +55,17 @@ export default class Scope {
|
||||||
|
|
||||||
set(key, value) {
|
set(key, value) {
|
||||||
let walk = this;
|
let walk = this;
|
||||||
|
// eslint-disable-next-line no-constant-condition
|
||||||
while (walk) {
|
while (walk) {
|
||||||
if (key in walk.context) {
|
if (key in walk.context) {
|
||||||
walk.context[key] = value;
|
walk.context[key] = value;
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
// TODO: option to disallow global set
|
||||||
|
if (!walk.parent) {
|
||||||
|
walk.context[key] = value;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
walk = walk.parent;
|
walk = walk.parent;
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user