refactor: behavior
This commit is contained in:
parent
de5b51b8ea
commit
abbab55d30
|
@ -1,18 +1,15 @@
|
|||
import {compose, EventEmitter, TickingPromise} from '@avocado/core';
|
||||
|
||||
import {Traversal} from './traversal';
|
||||
import {Traversals} from './traversals';
|
||||
|
||||
const decorate = compose(
|
||||
EventEmitter,
|
||||
);
|
||||
|
||||
export class Actions extends decorate(Traversals) {
|
||||
class Actions {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
constructor(expressions) {
|
||||
this.expressions = 'function' === typeof expressions ? expressions() : expressions;
|
||||
this._index = 0;
|
||||
this._actionPromise = null;
|
||||
this.promise = null;
|
||||
}
|
||||
|
||||
emitFinished() {
|
||||
|
@ -33,29 +30,24 @@ export class Actions extends decorate(Traversals) {
|
|||
|
||||
tick(context, elapsed) {
|
||||
// Empty resolves immediately.
|
||||
if (this.traversals.length === 0) {
|
||||
if (this.expressions.length === 0) {
|
||||
this.emitFinished();
|
||||
return;
|
||||
}
|
||||
// If the action promise ticks, tick it.
|
||||
if (this._actionPromise && this._actionPromise instanceof TickingPromise) {
|
||||
this._actionPromise.tick(elapsed);
|
||||
if (this.promise && this.promise instanceof TickingPromise) {
|
||||
this.promise.tick(elapsed);
|
||||
return;
|
||||
}
|
||||
// Actions execute immediately until a promise is made, or they're all
|
||||
// executed.
|
||||
while (true) {
|
||||
// Run the action.
|
||||
const result = this.traversals[this.index].traverse(context);
|
||||
const result = this.expressions[this.index](context);
|
||||
// Deferred result.
|
||||
if (result instanceof Promise) {
|
||||
this._actionPromise = result;
|
||||
// Handle any errors.
|
||||
result.catch(console.error);
|
||||
result.finally(() => {
|
||||
// Finally, run the prologue.
|
||||
this.prologue();
|
||||
});
|
||||
this.promise = result;
|
||||
this.promise.catch(console.error).finally(() => this.prologue());
|
||||
break;
|
||||
}
|
||||
// Immediate result.
|
||||
|
@ -68,19 +60,24 @@ export class Actions extends decorate(Traversals) {
|
|||
}
|
||||
|
||||
parallel(context) {
|
||||
// Map all traversals to results.
|
||||
const results = this.traversals.map((traversal) => {
|
||||
return traversal.traverse(context);
|
||||
});
|
||||
// Wrap all results in a TickingPromise.
|
||||
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() {
|
||||
// Clear out the action promise.
|
||||
this._actionPromise = null;
|
||||
this.promise = null;
|
||||
// Increment and wrap the index.
|
||||
this.index = (this.index + 1) % this.traversals.length;
|
||||
this.index = (this.index + 1) % this.expressions.length;
|
||||
// If rolled over, the actions are finished.
|
||||
if (0 === this.index) {
|
||||
this.emitFinished();
|
||||
|
@ -103,3 +100,5 @@ export class Actions extends decorate(Traversals) {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default decorate(Actions);
|
|
@ -1,36 +1,29 @@
|
|||
export function buildTraversal(path, value) {
|
||||
const traversal = {
|
||||
type: 'traversal',
|
||||
steps: path.map((key) => {
|
||||
return {
|
||||
type: 'key',
|
||||
key: key,
|
||||
};
|
||||
}),
|
||||
export function buildExpression(path, value) {
|
||||
const expression = {
|
||||
type: 'expression',
|
||||
ops: path.map((key) => ({type: 'key', key: key})),
|
||||
};
|
||||
if ('undefined' !== typeof value) {
|
||||
traversal.value = buildValue(value);
|
||||
expression.value = buildValue(value);
|
||||
}
|
||||
return traversal;
|
||||
return expression;
|
||||
}
|
||||
|
||||
export function buildInvoke (path, args = []) {
|
||||
const traversal = buildTraversal(path);
|
||||
traversal.steps.push({
|
||||
const expression = buildExpression(path);
|
||||
expression.ops.push({
|
||||
type: 'invoke',
|
||||
args: args.map((arg) => {
|
||||
return buildValue(arg);
|
||||
}),
|
||||
args: args.map((arg) => buildValue(arg)),
|
||||
});
|
||||
return traversal;
|
||||
return expression;
|
||||
}
|
||||
|
||||
export function buildValue(value) {
|
||||
if (
|
||||
'object' === typeof value
|
||||
&& (
|
||||
'traversal' === value.type
|
||||
|| 'actions' === value.type
|
||||
'expression' === value.type
|
||||
|| 'expressions' === value.type
|
||||
|| 'condition' === value.type
|
||||
)
|
||||
) {
|
||||
|
@ -46,8 +39,6 @@ export function buildCondition(operator, operands) {
|
|||
return {
|
||||
type: 'condition',
|
||||
operator,
|
||||
operands: operands.map((operand) => {
|
||||
return buildValue(operand);
|
||||
}),
|
||||
operands: operands.map((operand) => buildValue(operand)),
|
||||
};
|
||||
}
|
||||
|
|
26
packages/behavior/compile.js
Normal file
26
packages/behavior/compile.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
import memoize from 'lodash.memoize';
|
||||
import {invokeHookFlat, registerHooks} from 'scwp';
|
||||
|
||||
export const compilers = memoize(() => (
|
||||
invokeHookFlat('behaviorCompilers')
|
||||
.reduce((r, results) => ({
|
||||
...r,
|
||||
...results,
|
||||
}), {})
|
||||
));
|
||||
|
||||
export default function compile(variant) {
|
||||
const {[variant.type]: compiler} = compilers();
|
||||
if (!compiler) {
|
||||
return () => Promise.reject(new Error(`No compiler for '${variant.type}'`));
|
||||
}
|
||||
return compiler(variant);
|
||||
}
|
||||
|
||||
registerHooks({
|
||||
autoreg$accept: (type, M) => {
|
||||
if ('hooks' === type && 'behaviorCompilers' in M) {
|
||||
compilers.cache.clear();
|
||||
}
|
||||
},
|
||||
}, module.id);
|
142
packages/behavior/compilers.hooks.js
Normal file
142
packages/behavior/compilers.hooks.js
Normal file
|
@ -0,0 +1,142 @@
|
|||
import {fastApply} from '@avocado/core';
|
||||
|
||||
import compile from './compile';
|
||||
|
||||
const render = (ops) => ops.reduce((rendered, op) => {
|
||||
switch (op.type) {
|
||||
case 'key':
|
||||
return `${rendered ? `${rendered}.` : ''}${op.key}`;
|
||||
case 'invoke':
|
||||
return `${rendered}(${op.args.length > 0 ? '...' : ''})`;
|
||||
default:
|
||||
return rendered;
|
||||
}
|
||||
}, '');
|
||||
|
||||
function compileOp(op) {
|
||||
let args;
|
||||
if ('invoke' === op.type) {
|
||||
args = op.args.map(compile);
|
||||
}
|
||||
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((fn) => fn(context)).concat(context);
|
||||
// Promises are resolved transparently.
|
||||
const apply = (args) => fastApply(previous, current, args);
|
||||
return evaluated.some((arg) => arg instanceof Promise)
|
||||
? Promise.all(evaluated).then(apply)
|
||||
: apply(evaluated);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function disambiguateResult(value, fn) {
|
||||
const apply = (value) => fn(value);
|
||||
return value instanceof Promise ? value.then(apply) : apply(value);
|
||||
}
|
||||
|
||||
function compileExpression(expression) {
|
||||
const assign = 'undefined' !== typeof expression.assign
|
||||
? compile(expression.assign)
|
||||
: undefined;
|
||||
const ops = expression.ops.map(compileOp);
|
||||
const {ops: rawOps} = expression;
|
||||
return (context) => {
|
||||
let previous = null;
|
||||
let shorted = false;
|
||||
const [first, ...rest] = ops;
|
||||
return rest.reduce((current, op, index) => disambiguateResult(current, (current) => {
|
||||
if (shorted) {
|
||||
return undefined;
|
||||
}
|
||||
let next = undefined;
|
||||
const isLastOp = index === ops.length - 2;
|
||||
if (!isLastOp || !assign) {
|
||||
if ('undefined' === typeof current) {
|
||||
const rendered = render(rawOps.slice(0, index + 2));
|
||||
next = Promise.reject(new Error(`'${rendered}' is undefined`));
|
||||
shorted = true;
|
||||
}
|
||||
else {
|
||||
next = op(context, previous, current);
|
||||
}
|
||||
}
|
||||
else {
|
||||
const rawOp = rawOps[index + 1];
|
||||
switch (rawOp.type) {
|
||||
case 'key':
|
||||
if ('object' === typeof current) {
|
||||
current[rawOp.key] = assign(context);
|
||||
next = undefined;
|
||||
}
|
||||
break;
|
||||
case 'invoke':
|
||||
const rendered = render(rawOps.slice(0, index + 2));
|
||||
next = Promise.reject(new Error(`invalid assignment to function '${rendered}'`));
|
||||
break;
|
||||
}
|
||||
}
|
||||
previous = current;
|
||||
current = next;
|
||||
return current;
|
||||
}), context.getValue(rawOps[0].key));
|
||||
};
|
||||
}
|
||||
|
||||
function compileCondition(condition) {
|
||||
const {operator} = condition;
|
||||
const operands = condition.operands.map(compile);
|
||||
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);
|
||||
const needle = operands[1](context);
|
||||
return -1 !== haystack.indexOf(needle);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function behaviorCompilers() {
|
||||
return {
|
||||
condition: (condition) => compileCondition(condition),
|
||||
literal: ({value}) => (context) => value,
|
||||
expression: (expression) => compileExpression(expression),
|
||||
expressions: ({expressions}) => () => expressions.map(compileExpression),
|
||||
};
|
||||
}
|
74
packages/behavior/context.js
Normal file
74
packages/behavior/context.js
Normal file
|
@ -0,0 +1,74 @@
|
|||
import {arrayUnique, flatten, mapObject} from '@avocado/core';
|
||||
import memoize from 'lodash.memoize';
|
||||
import {invokeHookFlat, registerHooks} from 'scwp';
|
||||
|
||||
export const globals = memoize(() => (
|
||||
invokeHookFlat('behaviorContextGlobals')
|
||||
.reduce((r, results) => ({
|
||||
...r,
|
||||
...results,
|
||||
}), {})
|
||||
));
|
||||
|
||||
export default class Context {
|
||||
|
||||
constructor(defaults = {}) {
|
||||
this.map = new Map();
|
||||
this.clear();
|
||||
this.addObjectMap(defaults);
|
||||
}
|
||||
|
||||
add(key, value, type = 'undefined') {
|
||||
this.map.set(key, [value, type]);
|
||||
}
|
||||
|
||||
addObjectMap(map) {
|
||||
Object.entries(map)
|
||||
.forEach(([key, [variable, type]]) => (
|
||||
this.add(key, variable, type)
|
||||
));
|
||||
}
|
||||
|
||||
all() {
|
||||
return Array.from(this.map.keys())
|
||||
.reduce((r, key) => ({
|
||||
...r,
|
||||
[key]: this.get(key),
|
||||
}), {});
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.destroy();
|
||||
this.add('context', this, 'context');
|
||||
this.addObjectMap(globals());
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.map.clear();
|
||||
}
|
||||
|
||||
get(key) {
|
||||
return this.has(key) ? this.map.get(key) : [undefined, 'undefined'];
|
||||
}
|
||||
|
||||
getValue(key) {
|
||||
return this.get(key)[0];
|
||||
}
|
||||
|
||||
getType(key) {
|
||||
return this.get(key)[1];
|
||||
}
|
||||
|
||||
has(key) {
|
||||
return this.map.has(key);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
registerHooks({
|
||||
autoreg$accept: (type, M) => {
|
||||
if ('hooks' === type && 'behaviorContextGlobals' in M) {
|
||||
globals.cache.clear();
|
||||
}
|
||||
},
|
||||
}, module.id);
|
|
@ -1,34 +0,0 @@
|
|||
import {Context} from './context';
|
||||
|
||||
export function behaviorContextTypes() {
|
||||
return {
|
||||
bool: {
|
||||
defaultLiteral: true,
|
||||
},
|
||||
context: {
|
||||
children: {
|
||||
add: {
|
||||
type: 'void',
|
||||
label: 'Add $2 as $1.',
|
||||
args: [
|
||||
['key', {
|
||||
type: 'string',
|
||||
}],
|
||||
['value', {
|
||||
type: 'any',
|
||||
}],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
number: {
|
||||
defaultLiteral: 0,
|
||||
},
|
||||
stream: {},
|
||||
string: {
|
||||
defaultLiteral: '',
|
||||
},
|
||||
void: {},
|
||||
};
|
||||
}
|
||||
|
|
@ -1,237 +0,0 @@
|
|||
import {arrayUnique, flatten, mapObject} from '@avocado/core';
|
||||
import D from 'debug';
|
||||
import {invokeHookFlat} from 'scwp';
|
||||
|
||||
import {fastApply, objectFromEntries} from '@avocado/core';
|
||||
|
||||
const debug = D('@avocado:behavior:context');
|
||||
|
||||
export class Context {
|
||||
|
||||
constructor(defaults = {}) {
|
||||
this.typeMap = new Map();
|
||||
this.variableMap = new Map();
|
||||
this.initialize();
|
||||
this.addObjectMap(defaults);
|
||||
}
|
||||
|
||||
static allTypesDigraph() {
|
||||
return Object.keys(this.types())
|
||||
.reduce((r, type) => ({...r, [type]: this.typeDigraph(type)}), {});
|
||||
}
|
||||
|
||||
static allTypesInvertedDigraph() {
|
||||
const digraph = this.allTypesDigraph();
|
||||
let inverted = {};
|
||||
const fromTypes = Object.keys(digraph);
|
||||
for (let i = 0; i < fromTypes.length; i++) {
|
||||
const fromType = fromTypes[i];
|
||||
const toTypes = digraph[fromType];
|
||||
for (let j = 0; j < toTypes.length; j++) {
|
||||
const toType = toTypes[j];
|
||||
inverted[toType] = (inverted[toType] || {});
|
||||
inverted[toType][fromType] = true;
|
||||
}
|
||||
}
|
||||
inverted = mapObject(inverted, (types) => Object.keys(types));
|
||||
inverted = mapObject(
|
||||
inverted,
|
||||
(types) => arrayUnique(flatten(types.map((type) => (inverted[type] || []).concat(type)))),
|
||||
);
|
||||
return inverted;
|
||||
}
|
||||
|
||||
static describe(description, fn) {
|
||||
const {type} = description;
|
||||
fn.type = type;
|
||||
return fn;
|
||||
}
|
||||
|
||||
static globals() {
|
||||
return invokeHookFlat('behaviorContextGlobals')
|
||||
.reduce((r, results) => ({...r, ...results}), {});
|
||||
}
|
||||
|
||||
static typeDigraph(type, marked = {}) {
|
||||
const types = this.types();
|
||||
if (marked[type] || !types[type]) {
|
||||
marked[type] = true;
|
||||
return [];
|
||||
}
|
||||
const description = this.typeDescription(type, undefined);
|
||||
const subtypes = Object.keys(
|
||||
Object.values(description.children)
|
||||
.reduce((r, spec) => ({...r, [spec.type]: true}), {})
|
||||
);
|
||||
subtypes.forEach((type) => marked[type] = true);
|
||||
return arrayUnique(
|
||||
flatten(
|
||||
subtypes.map(
|
||||
(type) => this.typeDigraph(type, marked),
|
||||
),
|
||||
).concat(subtypes),
|
||||
);
|
||||
}
|
||||
|
||||
static typeDescription(type, variable) {
|
||||
const types = this.types();
|
||||
if (!types[type]) {
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
children: {},
|
||||
type,
|
||||
...('function' === typeof types[type] ? types[type](variable) : types[type])
|
||||
};
|
||||
}
|
||||
|
||||
static types() {
|
||||
return invokeHookFlat('behaviorContextTypes')
|
||||
.reduce((r, results) => ({
|
||||
...r,
|
||||
...results,
|
||||
}), {});
|
||||
}
|
||||
|
||||
add(key, value, type) {
|
||||
this.typeMap.set(key, type);
|
||||
this.variableMap.set(key, value);
|
||||
}
|
||||
|
||||
addObjectMap(map) {
|
||||
Object.entries(map).forEach(([key, [variable, type]]) => this.add(key, variable, type));
|
||||
}
|
||||
|
||||
all() {
|
||||
return Array.from(this.variableMap.keys())
|
||||
.reduce((r, key) => ({...r, [key]: this.get(key)}), {});
|
||||
}
|
||||
|
||||
initialize() {
|
||||
this.typeMap.clear()
|
||||
this.variableMap.clear()
|
||||
this.add('context', this, 'context');
|
||||
this.addObjectMap(this.constructor.globals());
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.typeMap.clear()
|
||||
this.variableMap.clear();
|
||||
}
|
||||
|
||||
get(key) {
|
||||
const type = this.typeMap.has(key) ? this.typeMap.get(key) : 'undefined';
|
||||
return [
|
||||
this.variableMap.get(key),
|
||||
type,
|
||||
];
|
||||
}
|
||||
|
||||
static renderSteps(steps) {
|
||||
return steps.reduce((rendered, step) => {
|
||||
switch (step.type) {
|
||||
case 'key':
|
||||
if (rendered) {
|
||||
rendered += '.';
|
||||
}
|
||||
rendered += step.key;
|
||||
break;
|
||||
case 'invoke':
|
||||
rendered += `(${step.args.length > 0 ? '...' : ''})`;
|
||||
break;
|
||||
}
|
||||
return rendered;
|
||||
}, '');
|
||||
}
|
||||
|
||||
static renderStepsUntilNow(steps, step) {
|
||||
const stepsUntilNow = steps.slice(0, steps.indexOf(step));
|
||||
return this.renderSteps(stepsUntilNow);
|
||||
}
|
||||
|
||||
takeStep(value, fn) {
|
||||
if (value instanceof Promise) {
|
||||
return value.then((value) => {
|
||||
return fn(value);
|
||||
});
|
||||
}
|
||||
else {
|
||||
return fn(value);
|
||||
}
|
||||
}
|
||||
|
||||
traverse(traversal) {
|
||||
const {steps, value} = traversal;
|
||||
return this.traverseAndDo(steps, (node, previousNode, step, index) => {
|
||||
const isLastStep = index === steps.length - 2;
|
||||
// Traverse if we're not at the final step with a value.
|
||||
if (!isLastStep || !value) {
|
||||
return this.traverseOneStep(steps, previousNode, node, step);
|
||||
}
|
||||
// Try to set the value.
|
||||
switch (step.type) {
|
||||
case 'key':
|
||||
if ('object' === typeof node) {
|
||||
return node[step.key] = value.get(this);
|
||||
}
|
||||
case 'invoke':
|
||||
const rendered = this.constructor.renderStepsUntilNow(steps, step);
|
||||
debug(`invalid assignment to function "${rendered}"`);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
traverseAndDo(steps, fn) {
|
||||
const [first, ...rest] = steps;
|
||||
if ('key' !== first.type) {
|
||||
throw new TypeError(`First step in a traversal must be type "key"`);
|
||||
}
|
||||
let previousNode = null;
|
||||
return rest.reduce((node, step, index) => {
|
||||
return this.takeStep(node, (node) => {
|
||||
const result = fn(node, previousNode, step, index);
|
||||
previousNode = node;
|
||||
return result;
|
||||
});
|
||||
}, this.variableMap.get(first.key));
|
||||
}
|
||||
|
||||
traverseOneStep(steps, previousNode, node, step) {
|
||||
if ('undefined' === typeof node) {
|
||||
const rendered = this.constructor.renderStepsUntilNow(steps, step);
|
||||
debug(`"${rendered}" is traversed through but undefined`);
|
||||
return;
|
||||
}
|
||||
switch (step.type) {
|
||||
case 'key':
|
||||
return node[step.key];
|
||||
case 'invoke':
|
||||
const args = step.args.map((arg) => arg.get(this));
|
||||
// Pass the context itself as the last arg.
|
||||
args.push(this);
|
||||
// Any arg promises will be resolved; the arg values will be passed
|
||||
// transparently to the invocation.
|
||||
let hasPromise = false;
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (args[i] instanceof Promise) {
|
||||
hasPromise = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (hasPromise) {
|
||||
return Promise.all(args).then((args) => {
|
||||
return fastApply(previousNode, node, args);
|
||||
});
|
||||
}
|
||||
else {
|
||||
return fastApply(previousNode, node, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
import {Context} from './context';
|
||||
|
||||
class Flow {
|
||||
|
||||
static conditional(condition, actions, context) {
|
||||
if (condition.get(context)) {
|
||||
return actions.serial(context);
|
||||
}
|
||||
};
|
||||
|
||||
static parallel(actions, context) {
|
||||
return actions.parallel(context);
|
||||
}
|
||||
|
||||
static serial(actions, context) {
|
||||
return actions.serial(context);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export function behaviorContextGlobals() {
|
||||
return {
|
||||
Flow: [Flow, 'Flow'],
|
||||
};
|
||||
}
|
||||
|
||||
export function behaviorContextTypes() {
|
||||
return {
|
||||
Flow: {
|
||||
children: {
|
||||
conditional: {
|
||||
type: 'bool',
|
||||
label: 'If $1 then run $2.',
|
||||
args: [
|
||||
['condition', {
|
||||
type: 'condition',
|
||||
}],
|
||||
['actions', {
|
||||
type: 'actions',
|
||||
}],
|
||||
],
|
||||
},
|
||||
parallel: {
|
||||
type: 'void',
|
||||
label: 'Run $1 in parallel.',
|
||||
args: [
|
||||
['actions', {
|
||||
type: 'actions',
|
||||
}],
|
||||
],
|
||||
},
|
||||
serial: {
|
||||
type: 'void',
|
||||
label: 'Run $1 serially.',
|
||||
args: [
|
||||
['actions', {
|
||||
type: 'actions',
|
||||
}],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export {Context} from './context';
|
|
@ -1,41 +0,0 @@
|
|||
import {TickingPromise} from '@avocado/core';
|
||||
|
||||
class Timing {
|
||||
|
||||
static wait (duration) {
|
||||
return new TickingPromise(
|
||||
() => {},
|
||||
(elapsed, resolve) => {
|
||||
duration -= elapsed;
|
||||
if (duration <= 0) {
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export function behaviorContextTypes() {
|
||||
return {
|
||||
Timing: {
|
||||
children: {
|
||||
wait: {
|
||||
type: 'void',
|
||||
label: 'Wait for $1 seconds.',
|
||||
args: [
|
||||
['duration', {
|
||||
type: 'number',
|
||||
}],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function behaviorContextGlobals() {
|
||||
return {
|
||||
Timing: [Timing, 'Timing'],
|
||||
};
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
import {merge as mergeObject} from '@avocado/core';
|
||||
|
||||
class Utility {
|
||||
|
||||
static makeArray(...args) {
|
||||
// No context!
|
||||
args.pop();
|
||||
return args;
|
||||
};
|
||||
|
||||
static 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;
|
||||
};
|
||||
|
||||
static log(...args) {
|
||||
return console.log(...args);
|
||||
}
|
||||
|
||||
static merge(...args) {
|
||||
// No context!
|
||||
args.pop();
|
||||
return mergeObject(...args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export function behaviorContextTypes() {
|
||||
return {
|
||||
Utility: {
|
||||
children: {
|
||||
makeArray: {
|
||||
type: 'array',
|
||||
label: 'Make array.',
|
||||
args: [],
|
||||
},
|
||||
makeObject: {
|
||||
type: 'object',
|
||||
label: 'Make object.',
|
||||
args: [],
|
||||
},
|
||||
log: {
|
||||
type: 'void',
|
||||
label: 'Log.',
|
||||
args: [],
|
||||
},
|
||||
merge: {
|
||||
type: 'object',
|
||||
label: 'Merge objects.',
|
||||
args: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function behaviorContextGlobals() {
|
||||
return {
|
||||
Utility: [Utility, 'Utility'],
|
||||
};
|
||||
}
|
|
@ -1,10 +1,29 @@
|
|||
export {Context} from './context';
|
||||
export {
|
||||
fromJSON as behaviorItemFromJSON,
|
||||
} from './item/registry';
|
||||
default as Actions,
|
||||
} from './actions';
|
||||
|
||||
export {
|
||||
buildCondition,
|
||||
buildInvoke,
|
||||
buildTraversal,
|
||||
buildExpression,
|
||||
buildValue,
|
||||
} from './builders';
|
||||
|
||||
export {
|
||||
default as compile,
|
||||
compilers,
|
||||
} from './compile';
|
||||
|
||||
export {
|
||||
default as Context,
|
||||
globals as contextGlobals,
|
||||
} from './context';
|
||||
|
||||
export {
|
||||
candidates,
|
||||
description,
|
||||
digraph,
|
||||
fitsInto,
|
||||
invertedDigraph,
|
||||
types,
|
||||
} from './type';
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
import {Traversal} from './traversal';
|
||||
|
||||
export class Action extends Traversal {
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
...super.toJSON(),
|
||||
type: 'action',
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
import {expect} from 'chai';
|
||||
|
||||
import {TickingPromise} from '@avocado/core';
|
||||
|
||||
import {buildInvoke, buildTraversal, buildValue} from '../builders';
|
||||
import {Context} from '../context';
|
||||
import {Actions} from './actions';
|
||||
import './initialize'
|
||||
|
||||
describe('behavior', () => {
|
||||
describe('Actions', () => {
|
||||
const context = new Context();
|
||||
beforeEach(() => {
|
||||
context.clear();
|
||||
});
|
||||
it('may resolve immediately', (done) => {
|
||||
const actions = new Actions();
|
||||
let total = 0;
|
||||
const increment = () => total++;
|
||||
context.add('increment', increment);
|
||||
actions.fromJSON({
|
||||
type: 'actions',
|
||||
traversals: [
|
||||
buildTraversal(['increment']),
|
||||
buildTraversal(['increment']),
|
||||
buildTraversal(['increment']),
|
||||
],
|
||||
});
|
||||
actions.on('actionsFinished', done);
|
||||
actions.tick(context, 0);
|
||||
expect(actions.index).to.equal(0);
|
||||
expect(total).to.equal(3);
|
||||
});
|
||||
it('may defer', async () => {
|
||||
const actions = new Actions();
|
||||
let resolve;
|
||||
const promise = new Promise((_) => resolve = _);
|
||||
context.add('test', promise);
|
||||
context.add('test2', new Promise(() => {}));
|
||||
actions.fromJSON({
|
||||
type: 'actions',
|
||||
traversals: [
|
||||
buildTraversal(['test']),
|
||||
buildTraversal(['test2']),
|
||||
],
|
||||
});
|
||||
actions.tick(context, 0);
|
||||
// Waiting?
|
||||
expect(actions.index).to.equal(0);
|
||||
resolve();
|
||||
return promise.finally(() => {
|
||||
// Waiting on next...
|
||||
expect(actions.index).to.equal(1);
|
||||
});
|
||||
});
|
||||
it('may defer with ticking', async () => {
|
||||
const actions = new Actions();
|
||||
let resolve;
|
||||
let duration = 1;
|
||||
const tickingPromise = new TickingPromise(
|
||||
() => {},
|
||||
(elapsed, resolve) => {
|
||||
duration -= elapsed;
|
||||
if (duration <= 0) {
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
);
|
||||
context.add('test', tickingPromise);
|
||||
context.add('test2', new Promise(() => {}));
|
||||
actions.fromJSON({
|
||||
type: 'actions',
|
||||
traversals: [
|
||||
buildTraversal(['test']),
|
||||
buildTraversal(['test2']),
|
||||
],
|
||||
});
|
||||
actions.tick(context, 0);
|
||||
// Waiting?
|
||||
expect(actions.index).to.equal(0);
|
||||
actions.tick(context, 0.5);
|
||||
expect(actions.index).to.equal(0);
|
||||
actions.tick(context, 0.5);
|
||||
// Still gotta be waiting...
|
||||
expect(actions.index).to.equal(0);
|
||||
return tickingPromise.finally(() => {
|
||||
// Waiting on next...
|
||||
expect(duration).to.equal(0);
|
||||
expect(actions.index).to.equal(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,46 +0,0 @@
|
|||
import {fromJSON as behaviorItemFromJSON} from './registry';
|
||||
|
||||
export function Collection(type) {
|
||||
const plural = `${type}s`;
|
||||
return class Collection {
|
||||
|
||||
constructor() {
|
||||
this[plural] = [];
|
||||
}
|
||||
|
||||
clone(other) {
|
||||
if (0 === other[plural].length) {
|
||||
return;
|
||||
}
|
||||
const Item = other[plural][0].constructor;
|
||||
for (let i = 0; i < other[plural].length; ++i) {
|
||||
this[plural][i] = new Item();
|
||||
this[plural][i].clone(other[plural][i]);
|
||||
}
|
||||
}
|
||||
|
||||
createClone() {
|
||||
const Items = this.constructor;
|
||||
const items = new Items();
|
||||
items.clone(this);
|
||||
return items;
|
||||
}
|
||||
|
||||
count() {
|
||||
return this[plural].length;
|
||||
}
|
||||
|
||||
fromJSON(json) {
|
||||
this[plural] = [];
|
||||
for (const item of json[plural]) {
|
||||
this[plural].push(behaviorItemFromJSON(item));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return this[plural].map((item) => item.toJSON());
|
||||
}
|
||||
|
||||
};
|
||||
}
|
|
@ -1,106 +0,0 @@
|
|||
import {fromJSON as behaviorItemFromJSON} from './registry';
|
||||
|
||||
export class Condition {
|
||||
|
||||
constructor() {
|
||||
this.operator = '';
|
||||
this.operands = [];
|
||||
}
|
||||
|
||||
check(context) {
|
||||
return this.get(context);
|
||||
}
|
||||
|
||||
clone(other) {
|
||||
this.operator = other.operator;
|
||||
this.operands = other.operands.map((operand) => operand.clone());
|
||||
}
|
||||
|
||||
fromJSON(json) {
|
||||
this.operator = json.operator;
|
||||
this.operands = json.operands.map((operand) => {
|
||||
return behaviorItemFromJSON(operand);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
get(context) {
|
||||
switch (this.operator) {
|
||||
case 'is':
|
||||
return this.operands[0].get(context) === this.operands[1].get(context);
|
||||
case 'isnt':
|
||||
return this.operands[0].get(context) !== this.operands[1].get(context);
|
||||
case '>':
|
||||
return this.operands[0].get(context) > this.operands[1].get(context);
|
||||
case '>=':
|
||||
return this.operands[0].get(context) >= this.operands[1].get(context);
|
||||
case '<':
|
||||
return this.operands[0].get(context) < this.operands[1].get(context);
|
||||
case '<=':
|
||||
return this.operands[0].get(context) <= this.operands[1].get(context);
|
||||
|
||||
case 'or':
|
||||
if (0 === this.operands.length) {
|
||||
return true;
|
||||
}
|
||||
for (const operand of this.operands) {
|
||||
if (!!operand.get(context)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
case 'and':
|
||||
if (0 === this.operands.length) {
|
||||
return true;
|
||||
}
|
||||
for (const operand of this.operands) {
|
||||
if (!operand.get(context)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
case 'contains':
|
||||
if (this.operands.length < 2) {
|
||||
return false;
|
||||
}
|
||||
const haystack = this.operands[0].get(context);
|
||||
const needle = this.operands[1].get(context);
|
||||
return -1 !== haystack.indexOf(needle);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
operandCount() {
|
||||
return this.operands.length;
|
||||
}
|
||||
|
||||
operand(index) {
|
||||
return this.operands[index];
|
||||
}
|
||||
|
||||
operands() {
|
||||
return this.operands;
|
||||
}
|
||||
|
||||
operator() {
|
||||
return this.operator;
|
||||
}
|
||||
|
||||
setOperand(index, operand) {
|
||||
this.operands[index] = operand;
|
||||
}
|
||||
|
||||
setOperator(operator) {
|
||||
this.operator = operator;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
type: 'condition',
|
||||
operator: this.operator,
|
||||
operands: this.operands.map((operand) => operand.toJSON()),
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,109 +0,0 @@
|
|||
import {expect} from 'chai';
|
||||
|
||||
import {Condition} from './condition';
|
||||
import {Literal} from './literal';
|
||||
import {deregister, register} from './registry';
|
||||
|
||||
describe('behavior', () => {
|
||||
describe('condition', () => {
|
||||
it('can do binary compares', () => {
|
||||
const condition = new Condition();
|
||||
condition.setOperand(0, new Literal().fromJSON({
|
||||
type: 'literal',
|
||||
value: 500,
|
||||
}));
|
||||
condition.setOperand(1, new Literal().fromJSON({
|
||||
type: 'literal',
|
||||
value: 420,
|
||||
}));
|
||||
// Greater than.
|
||||
condition.setOperator('>');
|
||||
expect(condition.check()).to.be.true;
|
||||
// Less than.
|
||||
condition.setOperator('<');
|
||||
expect(condition.check()).to.be.false;
|
||||
// Greater than or equal.
|
||||
condition.setOperator('>=');
|
||||
expect(condition.check()).to.be.true;
|
||||
// Less than or equal.
|
||||
condition.setOperator('<=');
|
||||
expect(condition.check()).to.be.false;
|
||||
// Is.
|
||||
condition.setOperator('is');
|
||||
expect(condition.check()).to.be.false;
|
||||
// Is not.
|
||||
condition.setOperator('isnt');
|
||||
expect(condition.check()).to.be.true;
|
||||
});
|
||||
it('can do varnary compares', () => {
|
||||
const condition = new Condition();
|
||||
condition.setOperand(0, new Literal().fromJSON({
|
||||
type: 'literal',
|
||||
value: true,
|
||||
}));
|
||||
condition.setOperand(1, new Literal().fromJSON({
|
||||
type: 'literal',
|
||||
value: true,
|
||||
}));
|
||||
condition.setOperand(2, new Literal().fromJSON({
|
||||
type: 'literal',
|
||||
value: false,
|
||||
}));
|
||||
// AND, mixed.
|
||||
condition.setOperator('and');
|
||||
expect(condition.check()).to.be.false;
|
||||
// OR, mixed.
|
||||
condition.setOperator('or');
|
||||
expect(condition.check()).to.be.true;
|
||||
// OR, all true.
|
||||
condition.setOperand(2, new Literal().fromJSON({
|
||||
type: 'literal',
|
||||
value: true,
|
||||
}));
|
||||
expect(condition.check()).to.be.true;
|
||||
// AND, all true.
|
||||
condition.setOperator('and');
|
||||
expect(condition.check()).to.be.true;
|
||||
// AND, all false.
|
||||
for (const i of [0, 1, 2]) {
|
||||
condition.setOperand(i, new Literal().fromJSON({
|
||||
type: 'literal',
|
||||
value: false,
|
||||
}));
|
||||
}
|
||||
expect(condition.check()).to.be.false;
|
||||
// OR, all false.
|
||||
condition.setOperator('or');
|
||||
expect(condition.check()).to.be.false;
|
||||
});
|
||||
describe('JSON', () => {
|
||||
beforeEach(() => {
|
||||
register(Literal);
|
||||
});
|
||||
afterEach(() => {
|
||||
deregister(Literal);
|
||||
});
|
||||
it('can instantiate operands', () => {
|
||||
const condition = new Condition();
|
||||
// Greater than.
|
||||
condition.fromJSON({
|
||||
operator: '>',
|
||||
operands: [
|
||||
{
|
||||
type: 'literal',
|
||||
value: 500,
|
||||
},
|
||||
{
|
||||
type: 'literal',
|
||||
value: 420,
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(condition.check()).to.be.true;
|
||||
// Less than.
|
||||
condition.setOperator('<');
|
||||
expect(condition.check()).to.be.false;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,14 +0,0 @@
|
|||
import {Collection} from './collection';
|
||||
|
||||
export class Conditions extends Collection('condition') {
|
||||
|
||||
check(context) {
|
||||
for (condition of this.conditions) {
|
||||
if (!this.conditions[index].check(context)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
import {Action} from './action';
|
||||
import {Actions} from './actions';
|
||||
import {Condition} from './condition';
|
||||
import {Conditions} from './conditions';
|
||||
import {Literal} from './literal';
|
||||
import {Routine} from './routine';
|
||||
import {Routines} from './routines';
|
||||
import {Traversal} from './traversal';
|
||||
import {Traversals} from './traversals';
|
||||
|
||||
export function avocadoBehaviorItems() {
|
||||
return {
|
||||
action: Action,
|
||||
actions: Actions,
|
||||
condition: Condition,
|
||||
conditions: Conditions,
|
||||
literal: Literal,
|
||||
routine: Routine,
|
||||
routines: Routines,
|
||||
traversal: Traversal,
|
||||
traversals: Traversals,
|
||||
};
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
export class Literal {
|
||||
|
||||
constructor() {
|
||||
this.value = null;
|
||||
}
|
||||
|
||||
clone(other) {
|
||||
this.value = other.value;
|
||||
}
|
||||
|
||||
fromJSON(json) {
|
||||
this.value = json.value;
|
||||
return this;
|
||||
}
|
||||
|
||||
get(context) {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
set(value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
type: 'literal',
|
||||
value: this.value,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
import {expect} from 'chai';
|
||||
|
||||
import {Literal} from './literal';
|
||||
|
||||
describe('behavior', () => {
|
||||
describe('Literal', () => {
|
||||
let literal = undefined;
|
||||
beforeEach(() => {
|
||||
literal = new Literal();
|
||||
literal.fromJSON({
|
||||
value: 69,
|
||||
});
|
||||
});
|
||||
it('can access literals', () => {
|
||||
expect(literal.get()).to.equal(69);
|
||||
});
|
||||
it('can modify literals', () => {
|
||||
literal.set(420);
|
||||
expect(literal.get()).to.equal(420);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,26 +0,0 @@
|
|||
import {invokeHookFlat} from 'scwp';
|
||||
|
||||
let _behaviorItems;
|
||||
|
||||
export function fromJSON({type, ...json}) {
|
||||
const items = behaviorItems();
|
||||
const Class = items[type];
|
||||
if (!Class) {
|
||||
throw new TypeError(`There is no class for the behavior item "${type}"`);
|
||||
}
|
||||
return (new Class()).fromJSON(json);
|
||||
}
|
||||
|
||||
function behaviorItems() {
|
||||
if (!_behaviorItems) {
|
||||
_behaviorItems = {};
|
||||
const itemsLists = invokeHookFlat('avocadoBehaviorItems');
|
||||
for (let i = 0; i < itemsLists.length; i++) {
|
||||
_behaviorItems = {
|
||||
..._behaviorItems,
|
||||
...itemsLists[i],
|
||||
};
|
||||
}
|
||||
}
|
||||
return _behaviorItems;
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
import {Actions} from './actions';
|
||||
|
||||
export class Routine {
|
||||
|
||||
constructor() {
|
||||
this.actions = new Actions();
|
||||
}
|
||||
|
||||
clone(other) {
|
||||
this.actions = other.actions.clone();
|
||||
}
|
||||
|
||||
fromJSON(json) {
|
||||
this.actions.fromJSON(json.routine);
|
||||
return this;
|
||||
}
|
||||
|
||||
tick(context, elapsed) {
|
||||
this.actions.tick(context, elapsed);
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
type: 'routine',
|
||||
actions: this.actions.toJSON(),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
import {Routine} from './routine';
|
||||
|
||||
export class Routines {
|
||||
|
||||
constructor() {
|
||||
this.routines = {};
|
||||
}
|
||||
|
||||
*[Symbol.iterator]() {
|
||||
for (const index in this.routines) {
|
||||
const routine = this.routines[index];
|
||||
yield routine;
|
||||
}
|
||||
}
|
||||
|
||||
clone(other) {
|
||||
for (const i in other.routines) {
|
||||
const routine = other.routines[i];
|
||||
this.routines[i] = routine.clone();
|
||||
}
|
||||
}
|
||||
|
||||
fromJSON(json) {
|
||||
for (const i in json.routines) {
|
||||
const routineJSON = json.routines[i];
|
||||
this.routines[i] = (new Routine()).fromJSON(routineJSON);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
routine(index) {
|
||||
return this.routines[index];
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
const routines = {};
|
||||
for (const i in this.routines) {
|
||||
routines[i] = this.routines[i].toJSON();
|
||||
}
|
||||
return {
|
||||
type: 'routines',
|
||||
routines,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
import {fromJSON as behaviorItemFromJSON} from './registry';
|
||||
|
||||
export class Traversal {
|
||||
|
||||
constructor(json) {
|
||||
this.steps = [];
|
||||
this.value = undefined;
|
||||
if (json) {
|
||||
this.fromJSON(json);
|
||||
}
|
||||
}
|
||||
|
||||
clone(other) {
|
||||
this.steps = other.steps.map((step) => {
|
||||
switch (step.type) {
|
||||
case 'key':
|
||||
return step;
|
||||
case 'invoke':
|
||||
return {
|
||||
type: 'invoke',
|
||||
args: step.args.map((arg, index) => {
|
||||
const Item = arg.constructor;
|
||||
const item = new Item();
|
||||
item.clone(arg);
|
||||
return item;
|
||||
}),
|
||||
};
|
||||
}
|
||||
});
|
||||
if (other.value) {
|
||||
this.value = other.value.clone();
|
||||
}
|
||||
}
|
||||
|
||||
fromJSON(json) {
|
||||
this.steps = json.steps.map((step) => {
|
||||
switch (step.type) {
|
||||
case 'key':
|
||||
return step;
|
||||
case 'invoke':
|
||||
return {
|
||||
type: 'invoke',
|
||||
args: step.args.map((arg) => behaviorItemFromJSON(arg)),
|
||||
};
|
||||
}
|
||||
});
|
||||
if (json.value) {
|
||||
this.value = behaviorItemFromJSON(json.value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
get(context) {
|
||||
return this.traverse(context);
|
||||
}
|
||||
|
||||
traverse(context) {
|
||||
if (context) {
|
||||
return context.traverse(this);
|
||||
}
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
type: 'traversal',
|
||||
steps: this.steps.map((step) => {
|
||||
switch (step.type) {
|
||||
case 'key':
|
||||
return step;
|
||||
case 'invoke':
|
||||
return {
|
||||
type: 'invoke',
|
||||
args: step.args.map((arg) => arg.toJSON()),
|
||||
};
|
||||
}
|
||||
}),
|
||||
value: this.value ? this.value.toJSON() : undefined,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
import {TickingPromise} from '@avocado/core';
|
||||
|
||||
import {Collection} from './collection';
|
||||
import {Traversal} from './traversal';
|
||||
|
||||
export class Traversals extends Collection('traversal') {
|
||||
|
||||
parallel(context) {
|
||||
const results = this.traversals.map((traversal) => {
|
||||
return traversal.traverse(context);
|
||||
});
|
||||
// Early out if no promises.
|
||||
if (!results.reduce((has, result) => {
|
||||
return has || result instanceof Promise;
|
||||
}, false)) {
|
||||
return results;
|
||||
}
|
||||
return TickingPromise.all(results);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
import {compose, flatten} from '@avocado/core';
|
||||
import {compose, flatten, mapObject} from '@avocado/core';
|
||||
import {StateProperty, Trait} from '@avocado/entity';
|
||||
|
||||
import {Context} from '../context';
|
||||
import {Routines} from '../item/routines';
|
||||
import Actions from '../actions';
|
||||
import compile from '../compile';
|
||||
import Context from '../context';
|
||||
|
||||
const decorate = compose(
|
||||
StateProperty('currentRoutine', {
|
||||
|
@ -13,7 +14,7 @@ const decorate = compose(
|
|||
|
||||
export default class Behaved extends decorate(Trait) {
|
||||
|
||||
static behaviorContextTypes() {
|
||||
static behaviorTypes() {
|
||||
return {
|
||||
context: {
|
||||
type: 'context',
|
||||
|
@ -67,7 +68,10 @@ export default class Behaved extends decorate(Trait) {
|
|||
entity: [this.entity, 'entity'],
|
||||
});
|
||||
this._currentRoutine = undefined;
|
||||
this._routines = (new Routines()).fromJSON(this.params.routines);
|
||||
this._routines = mapObject(
|
||||
this.params.routines,
|
||||
(routine) => new Actions(compile(routine))
|
||||
);
|
||||
this.updateCurrentRoutine(this.state.currentRoutine);
|
||||
}
|
||||
|
||||
|
@ -82,7 +86,7 @@ export default class Behaved extends decorate(Trait) {
|
|||
}
|
||||
|
||||
updateCurrentRoutine(currentRoutine) {
|
||||
this._currentRoutine = this._routines.routine(currentRoutine);
|
||||
this._currentRoutine = this._routines[currentRoutine];
|
||||
}
|
||||
|
||||
listeners() {
|
||||
|
|
108
packages/behavior/type.js
Normal file
108
packages/behavior/type.js
Normal file
|
@ -0,0 +1,108 @@
|
|||
import {arrayUnique, flatten, mapObject} from '@avocado/core';
|
||||
import memoize from 'lodash.memoize';
|
||||
import {invokeHookFlat, registerHooks} from 'scwp';
|
||||
|
||||
export const types = memoize(() => {
|
||||
return invokeHookFlat('behaviorTypes')
|
||||
.reduce((r, results) => ({
|
||||
...r,
|
||||
...results,
|
||||
}), {});
|
||||
});
|
||||
|
||||
function typeDigraph(type, marked = {}) {
|
||||
const allTypes = types();
|
||||
if (marked[type] || !allTypes[type]) {
|
||||
marked[type] = true;
|
||||
return [];
|
||||
}
|
||||
const description = description(type, undefined);
|
||||
const subtypes = Object.keys(
|
||||
Object.values(description.children)
|
||||
.reduce((r, {type: childType}) => ({
|
||||
...r,
|
||||
[childType]: true,
|
||||
}), {})
|
||||
);
|
||||
subtypes.forEach((type) => marked[type] = true);
|
||||
return arrayUnique(flatten(subtypes.map((type) => typeDigraph(type, marked))).concat(subtypes));
|
||||
}
|
||||
|
||||
export const digraph = memoize(() => {
|
||||
return Object.keys(allTypes())
|
||||
.reduce((r, type) => ({
|
||||
...r,
|
||||
[type]: typeDigraph(type),
|
||||
}), {});
|
||||
});
|
||||
|
||||
export const invertedDigraph = memoize(() => {
|
||||
const digraph = digraph();
|
||||
let inverted = {};
|
||||
const fromTypes = Object.keys(digraph);
|
||||
for (let i = 0; i < fromTypes.length; i++) {
|
||||
const fromType = fromTypes[i];
|
||||
const toTypes = digraph[fromType];
|
||||
for (let j = 0; j < toTypes.length; j++) {
|
||||
const toType = toTypes[j];
|
||||
inverted[toType] = (inverted[toType] || {});
|
||||
inverted[toType][fromType] = true;
|
||||
}
|
||||
}
|
||||
inverted = mapObject(inverted, (types) => Object.keys(types));
|
||||
inverted = mapObject(inverted, (types) => (
|
||||
arrayUnique(flatten(types.map((type) => (inverted[type] || []).concat(type))))
|
||||
));
|
||||
return inverted;
|
||||
});
|
||||
|
||||
export const candidates = (description, type) => {
|
||||
const inverted = invertedDigraph();
|
||||
const types = (inverted[type] || []).concat(type);
|
||||
const {children} = description;
|
||||
return 'any' === type
|
||||
? Object.keys(children)
|
||||
: Object.entries(children)
|
||||
.reduce((r, [key, {type}]) => (
|
||||
r.concat(
|
||||
types.some((candidate) => typeFits(candidate, type))
|
||||
? [key]
|
||||
: []
|
||||
)
|
||||
), []);
|
||||
};
|
||||
|
||||
export function description(type, instance) {
|
||||
const allTypes = types();
|
||||
const defaults = {
|
||||
children: {},
|
||||
type,
|
||||
};
|
||||
if (!allTypes[type]) {
|
||||
return {
|
||||
...defaults,
|
||||
type: 'undefined',
|
||||
};
|
||||
}
|
||||
return {
|
||||
...defaults,
|
||||
...('function' === typeof allTypes[type] ? allTypes[type](variable) : allTypes[type])
|
||||
};
|
||||
}
|
||||
|
||||
export function fitsInto(candidate, reference) {
|
||||
if ('any' === reference) {
|
||||
return true;
|
||||
}
|
||||
return -1 !== reference.split('|').map((type) => type.trim()).indexOf(candidate);
|
||||
}
|
||||
|
||||
registerHooks({
|
||||
autoreg$accept: (type, M) => {
|
||||
if ('hooks' === type && 'behaviorTypes' in M) {
|
||||
allTypes.cache.clear();
|
||||
digraph.cache.clear();
|
||||
invertedDigraph.cache.clear();
|
||||
}
|
||||
},
|
||||
}, module.id);
|
114
packages/behavior/types.hooks.js
Normal file
114
packages/behavior/types.hooks.js
Normal file
|
@ -0,0 +1,114 @@
|
|||
import Flow from './types/flow';
|
||||
import Timing from './types/timing';
|
||||
import Utility from './types/utility';
|
||||
|
||||
export function behaviorContextGlobals() {
|
||||
return {
|
||||
Flow: [Flow, 'Flow'],
|
||||
Timing: [Timing, 'Timing'],
|
||||
Utility: [Utility, 'Utility'],
|
||||
};
|
||||
}
|
||||
|
||||
export function behaviorTypes() {
|
||||
return {
|
||||
bool: {
|
||||
defaultLiteral: true,
|
||||
},
|
||||
context: {
|
||||
children: {
|
||||
add: {
|
||||
type: 'void',
|
||||
label: 'Add $2 as $1.',
|
||||
args: [
|
||||
['key', {
|
||||
type: 'string',
|
||||
}],
|
||||
['value', {
|
||||
type: 'any',
|
||||
}],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
Flow: {
|
||||
children: {
|
||||
conditional: {
|
||||
type: 'bool',
|
||||
label: 'If $1 then run $2.',
|
||||
args: [
|
||||
['condition', {
|
||||
type: 'condition',
|
||||
}],
|
||||
['actions', {
|
||||
type: 'actions',
|
||||
}],
|
||||
],
|
||||
},
|
||||
parallel: {
|
||||
type: 'void',
|
||||
label: 'Run $1 in parallel.',
|
||||
args: [
|
||||
['actions', {
|
||||
type: 'actions',
|
||||
}],
|
||||
],
|
||||
},
|
||||
serial: {
|
||||
type: 'void',
|
||||
label: 'Run $1 serially.',
|
||||
args: [
|
||||
['actions', {
|
||||
type: 'actions',
|
||||
}],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
number: {
|
||||
defaultLiteral: 0,
|
||||
},
|
||||
stream: {},
|
||||
string: {
|
||||
defaultLiteral: '',
|
||||
},
|
||||
Timing: {
|
||||
children: {
|
||||
wait: {
|
||||
type: 'void',
|
||||
label: 'Wait for $1 seconds.',
|
||||
args: [
|
||||
['duration', {
|
||||
type: 'number',
|
||||
}],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
Utility: {
|
||||
children: {
|
||||
makeArray: {
|
||||
type: 'array',
|
||||
label: 'Make array.',
|
||||
args: [],
|
||||
},
|
||||
makeObject: {
|
||||
type: 'object',
|
||||
label: 'Make object.',
|
||||
args: [],
|
||||
},
|
||||
log: {
|
||||
type: 'void',
|
||||
label: 'Log.',
|
||||
args: [],
|
||||
},
|
||||
merge: {
|
||||
type: 'object',
|
||||
label: 'Merge objects.',
|
||||
args: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
void: {},
|
||||
};
|
||||
}
|
19
packages/behavior/types/flow.js
Normal file
19
packages/behavior/types/flow.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
import Actions from '../actions';
|
||||
|
||||
export default class Flow {
|
||||
|
||||
static conditional(condition, expressions, context) {
|
||||
if (condition.get(context)) {
|
||||
return (new Actions(expressions)).serial(context);
|
||||
}
|
||||
};
|
||||
|
||||
static parallel(expressions, context) {
|
||||
return (new Actions(expressions)).parallel(context);
|
||||
}
|
||||
|
||||
static serial(expressions, context) {
|
||||
return (new Actions(expressions)).serial(context);
|
||||
}
|
||||
|
||||
}
|
17
packages/behavior/types/timing.js
Normal file
17
packages/behavior/types/timing.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
import {TickingPromise} from '@avocado/core';
|
||||
|
||||
export default class Timing {
|
||||
|
||||
static wait (duration) {
|
||||
return new TickingPromise(
|
||||
() => {},
|
||||
(elapsed, resolve) => {
|
||||
duration -= elapsed;
|
||||
if (duration <= 0) {
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
}
|
33
packages/behavior/types/utility.js
Normal file
33
packages/behavior/types/utility.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
import {merge as mergeObject} from '@avocado/core';
|
||||
|
||||
export default class Utility {
|
||||
|
||||
static makeArray(...args) {
|
||||
// No context!
|
||||
args.pop();
|
||||
return args;
|
||||
};
|
||||
|
||||
static 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;
|
||||
};
|
||||
|
||||
static log(...args) {
|
||||
return console.log(...args);
|
||||
}
|
||||
|
||||
static merge(...args) {
|
||||
// No context!
|
||||
args.pop();
|
||||
return mergeObject(...args);
|
||||
}
|
||||
|
||||
}
|
|
@ -243,11 +243,12 @@ export default class Entity extends decorate(Resource) {
|
|||
promises.push(this._traitsFlat[i].hydrate());
|
||||
}
|
||||
this._hydrationPromise = Promise.all(promises);
|
||||
}
|
||||
return this._hydrationPromise.then(() => {
|
||||
this._hydrationPromise.then(() => {
|
||||
this.tick(0);
|
||||
});
|
||||
}
|
||||
return this._hydrationPromise;
|
||||
}
|
||||
|
||||
invokeHook(hook, ...args) {
|
||||
const results = {};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export function behaviorContextTypes() {
|
||||
export function behaviorTypes() {
|
||||
return {
|
||||
entity: (entity) => {
|
||||
const {allTraits} = require('./trait/registrar');
|
||||
|
@ -20,7 +20,7 @@ export function behaviorContextTypes() {
|
|||
};
|
||||
return Traits
|
||||
.reduce((r, T) => ({
|
||||
...r, children: {...r.children, ...T.behaviorContextTypes(), ...T.describeState()},
|
||||
...r, children: {...r.children, ...T.behaviorTypes(), ...T.describeState()},
|
||||
}), core);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -71,6 +71,7 @@ export class EntityList extends decorate(class {}) {
|
|||
if (AVOCADO_SERVER) {
|
||||
this._informedEntities.set(entity, []);
|
||||
}
|
||||
entity.hydrate();
|
||||
entity.attachToList(this);
|
||||
entity.once('destroy', () => {
|
||||
this.removeEntity(entity);
|
||||
|
|
|
@ -38,7 +38,7 @@ export class Trait extends decorate(class {}) {
|
|||
this._fastDirtyCheck = false;
|
||||
}
|
||||
|
||||
static behaviorContextTypes() {
|
||||
static behaviorTypes() {
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import {
|
||||
behaviorItemFromJSON,
|
||||
Actions,
|
||||
buildCondition,
|
||||
buildInvoke,
|
||||
buildTraversal,
|
||||
buildExpression,
|
||||
compile,
|
||||
Context,
|
||||
} from '@avocado/behavior';
|
||||
import {compose} from '@avocado/core';
|
||||
|
@ -25,7 +26,7 @@ const decorate = compose(
|
|||
|
||||
export default class Alive extends decorate(Trait) {
|
||||
|
||||
static behaviorContextTypes() {
|
||||
static behaviorTypes() {
|
||||
return {
|
||||
deathSound: {
|
||||
type: 'string',
|
||||
|
@ -40,7 +41,7 @@ export default class Alive extends decorate(Trait) {
|
|||
|
||||
static defaultParams() {
|
||||
const playDeathSound = buildInvoke(['entity', 'playSound'], [
|
||||
buildTraversal(['entity', 'deathSound']),
|
||||
buildExpression(['entity', 'deathSound']),
|
||||
]);
|
||||
const squeeze = buildInvoke(['entity', 'transition'], [
|
||||
{
|
||||
|
@ -51,13 +52,13 @@ export default class Alive extends decorate(Trait) {
|
|||
0.2,
|
||||
]);
|
||||
const isLifeGone = buildCondition('<=', [
|
||||
buildTraversal(['entity', 'life']),
|
||||
buildExpression(['entity', 'life']),
|
||||
0,
|
||||
]);
|
||||
return {
|
||||
deathActions: {
|
||||
type: 'actions',
|
||||
traversals: [
|
||||
type: 'expressions',
|
||||
expressions: [
|
||||
playDeathSound,
|
||||
squeeze,
|
||||
],
|
||||
|
@ -118,8 +119,8 @@ export default class Alive extends decorate(Trait) {
|
|||
this._context = new Context({
|
||||
entity: [this.entity, 'entity'],
|
||||
});
|
||||
this._deathActions = behaviorItemFromJSON(this.params.deathActions);
|
||||
this._deathCondition = behaviorItemFromJSON(this.params.deathCondition);
|
||||
this._deathActions = new Actions(compile(this.params.deathActions));
|
||||
this._deathCondition = compile(this.params.deathCondition);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
|
@ -183,13 +184,13 @@ export default class Alive extends decorate(Trait) {
|
|||
return;
|
||||
}
|
||||
this.entity.isDying = true;
|
||||
const dyingTickingPromise = this._deathActions.tickingPromise(this._context);
|
||||
this.entity.addTickingPromise(dyingTickingPromise).then(() => {
|
||||
const diedPromises = this.entity.invokeHookFlat('died');
|
||||
Promise.all(diedPromises).then(() => {
|
||||
return this.entity.addTickingPromise(this._deathActions.tickingPromise(this._context))
|
||||
.then(() => (
|
||||
Promise.all(this.entity.invokeHookFlat('died'))
|
||||
.then(() => {
|
||||
this.entity.destroy();
|
||||
});
|
||||
});
|
||||
})
|
||||
));
|
||||
},
|
||||
|
||||
};
|
||||
|
@ -197,7 +198,7 @@ export default class Alive extends decorate(Trait) {
|
|||
|
||||
tick(elapsed) {
|
||||
if (AVOCADO_SERVER) {
|
||||
if (!this.entity.isDying && this._deathCondition.check(this._context)) {
|
||||
if (!this.entity.isDying && this._deathCondition(this._context)) {
|
||||
this.entity.forceDeath();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ const decorate = compose(
|
|||
|
||||
export default class Existent extends decorate(Trait) {
|
||||
|
||||
static behaviorContextTypes() {
|
||||
static behaviorTypes() {
|
||||
return {
|
||||
destroy: {
|
||||
type: 'void',
|
||||
|
|
|
@ -4,7 +4,7 @@ import {Trait} from '../trait';
|
|||
|
||||
export default class Listed extends Trait {
|
||||
|
||||
static behaviorContextTypes() {
|
||||
static behaviorTypes() {
|
||||
return {
|
||||
detachFromList: {
|
||||
type: 'void',
|
||||
|
|
|
@ -10,7 +10,7 @@ const decorate = compose(
|
|||
|
||||
export default class Mobile extends decorate(Trait) {
|
||||
|
||||
static behaviorContextTypes() {
|
||||
static behaviorTypes() {
|
||||
return {
|
||||
moveFor: {
|
||||
type: 'void',
|
||||
|
|
|
@ -17,7 +17,7 @@ const decorate = compose(
|
|||
// < 16768 will pack into 1 short per axe and give +/- 0.25 precision.
|
||||
export default class Positioned extends decorate(Trait) {
|
||||
|
||||
static behaviorContextTypes() {
|
||||
static behaviorTypes() {
|
||||
return {
|
||||
position: {
|
||||
type: 'vector',
|
||||
|
|
|
@ -13,7 +13,7 @@ const decorate = compose(
|
|||
|
||||
export default class Spawner extends decorate(Trait) {
|
||||
|
||||
static behaviorContextTypes() {
|
||||
static behaviorTypes() {
|
||||
return {
|
||||
killAllChildren: {
|
||||
type: 'void',
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import * as I from 'immutable';
|
||||
|
||||
import {compose, Property} from '@avocado/core';
|
||||
import {StateProperty, Trait} from '@avocado/entity';
|
||||
import {Rectangle, Vector} from '@avocado/math';
|
||||
|
@ -30,7 +28,7 @@ const decorate = compose(
|
|||
|
||||
export default class Visible extends decorate(Trait) {
|
||||
|
||||
static behaviorContextTypes() {
|
||||
static behaviorTypes() {
|
||||
return {
|
||||
updateVisibleBoundingBox: {
|
||||
advanced: true,
|
||||
|
|
|
@ -6,7 +6,7 @@ export function behaviorContextGlobals() {
|
|||
};
|
||||
}
|
||||
|
||||
export function behaviorContextTypes() {
|
||||
export function behaviorTypes() {
|
||||
return {
|
||||
Math: (Math) => ({
|
||||
children: {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export function behaviorContextTypes() {
|
||||
export function behaviorTypes() {
|
||||
return {
|
||||
vector: {
|
||||
defaultLiteral: [0, 0],
|
||||
|
|
|
@ -409,7 +409,7 @@ export class Range extends MathRange {
|
|||
|
||||
}
|
||||
|
||||
export function behaviorContextTypes() {
|
||||
export function behaviorTypes() {
|
||||
return {
|
||||
type: 'Vector',
|
||||
children: {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {behaviorItemFromJSON, Context} from '@avocado/behavior';
|
||||
import {Actions, compile, Context} from '@avocado/behavior';
|
||||
import {compose, TickingPromise} from '@avocado/core';
|
||||
import {StateProperty, Trait} from '@avocado/entity';
|
||||
import {Rectangle, Vector} from '@avocado/math';
|
||||
|
@ -10,7 +10,7 @@ const decorate = compose(
|
|||
|
||||
export default class Collider extends decorate(Trait) {
|
||||
|
||||
static behaviorContextTypes() {
|
||||
static behaviorTypes() {
|
||||
return {
|
||||
collidesWith: {
|
||||
advanced: true,
|
||||
|
@ -61,8 +61,14 @@ export default class Collider extends decorate(Trait) {
|
|||
collidesWithGroups: [
|
||||
'default',
|
||||
],
|
||||
collisionEndActions: undefined,
|
||||
collisionStartActions: undefined,
|
||||
collisionEndActions: {
|
||||
type: 'expressions',
|
||||
expressions: [],
|
||||
},
|
||||
collisionStartActions: {
|
||||
type: 'expressions',
|
||||
expressions: [],
|
||||
},
|
||||
collisionGroup: 'default',
|
||||
isSensor: false,
|
||||
}
|
||||
|
@ -123,22 +129,24 @@ export default class Collider extends decorate(Trait) {
|
|||
|
||||
constructor(entity, params, state) {
|
||||
super(entity, params, state);
|
||||
this._collidesWithGroups = this.params.collidesWithGroups;
|
||||
if (this.params.collisionEndActions) {
|
||||
this._collisionEndActions = behaviorItemFromJSON(
|
||||
this.params.collisionEndActions,
|
||||
);
|
||||
}
|
||||
if (this.params.collisionStartActions) {
|
||||
this._collisionStartActions = behaviorItemFromJSON(
|
||||
this.params.collisionStartActions,
|
||||
);
|
||||
}
|
||||
this._collisionGroup = this.params.collisionGroup;
|
||||
this._collisionTickingPromises = [];
|
||||
const {
|
||||
collidesWithGroups,
|
||||
collisionEndActions,
|
||||
collisionGroup,
|
||||
collisionStartActions,
|
||||
isSensor,
|
||||
} = this.params;
|
||||
this._collidesWithGroups = collidesWithGroups;
|
||||
this._collisionEndActions = collisionEndActions.length > 0
|
||||
? new Actions(compile(collisionEndActions))
|
||||
: undefined;
|
||||
this._collisionStartActions = collisionStartActions.length > 0
|
||||
? new Actions(compile(collisionStartActions))
|
||||
: undefined;
|
||||
this._collisionGroup = collisionGroup;
|
||||
this._doesNotCollideWith = [];
|
||||
this._isCollidingWith = [];
|
||||
this._isSensor = this.params.isSensor;
|
||||
this._isSensor = isSensor;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
|
@ -214,8 +222,7 @@ export default class Collider extends decorate(Trait) {
|
|||
entity: [this.entity, 'entity'],
|
||||
other: [this.entity, 'entity'],
|
||||
});
|
||||
const tickingPromise = actions.tickingPromise(context);
|
||||
this._collisionTickingPromises.push(tickingPromise);
|
||||
this.entity.addTickingPromise(actions.tickingPromise(context));
|
||||
}
|
||||
|
||||
releaseAllCollisions() {
|
||||
|
@ -243,10 +250,7 @@ export default class Collider extends decorate(Trait) {
|
|||
if (-1 !== index) {
|
||||
this._isCollidingWith.splice(index, 1);
|
||||
if (this._collisionEndActions) {
|
||||
this.pushCollisionTickingPromise(
|
||||
this._collisionEndActions,
|
||||
other
|
||||
);
|
||||
this.pushCollisionTickingPromise(this._collisionEndActions, other);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -256,10 +260,7 @@ export default class Collider extends decorate(Trait) {
|
|||
if (-1 === index) {
|
||||
this._isCollidingWith.push(other);
|
||||
if (this._collisionStartActions) {
|
||||
this.pushCollisionTickingPromise(
|
||||
this._collisionStartActions,
|
||||
other
|
||||
);
|
||||
this.pushCollisionTickingPromise(this._collisionStartActions, other);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -306,9 +307,6 @@ export default class Collider extends decorate(Trait) {
|
|||
|
||||
tick(elapsed) {
|
||||
if (AVOCADO_SERVER) {
|
||||
for (let i = 0; i < this._collisionTickingPromises.length; ++i) {
|
||||
this._collisionTickingPromises[i].tick(elapsed);
|
||||
}
|
||||
this.checkActiveCollision();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ const decorate = compose(
|
|||
|
||||
export default class Emitted extends decorate(Trait) {
|
||||
|
||||
static behaviorContextTypes() {
|
||||
static behaviorTypes() {
|
||||
return {
|
||||
particle: {
|
||||
type: 'particle',
|
||||
|
|
|
@ -14,7 +14,7 @@ const decorate = compose(
|
|||
|
||||
export default class Emitter extends decorate(Trait) {
|
||||
|
||||
static behaviorContextTypes() {
|
||||
static behaviorTypes() {
|
||||
return {
|
||||
emitParticleEntity: {
|
||||
cycle: true,
|
||||
|
|
|
@ -12,7 +12,7 @@ const decorate = compose(
|
|||
|
||||
export default class Physical extends decorate(Trait) {
|
||||
|
||||
static behaviorContextTypes() {
|
||||
static behaviorTypes() {
|
||||
return {
|
||||
applyForce: {
|
||||
type: 'void',
|
||||
|
|
|
@ -4,7 +4,7 @@ import {Sound} from '..';
|
|||
|
||||
export default class Audible extends Trait {
|
||||
|
||||
static behaviorContextTypes() {
|
||||
static behaviorTypes() {
|
||||
return {
|
||||
hasSound: {
|
||||
type: 'bool',
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export function behaviorContextTypes() {
|
||||
export function behaviorTypes() {
|
||||
return {
|
||||
layer: (layer) => {
|
||||
return {
|
||||
|
|
|
@ -3,7 +3,7 @@ import {Vector} from '@avocado/math';
|
|||
|
||||
export default class Layered extends Trait {
|
||||
|
||||
static behaviorContextTypes() {
|
||||
static behaviorTypes() {
|
||||
return {
|
||||
layer: {
|
||||
type: 'layer',
|
||||
|
|
|
@ -2,7 +2,7 @@ import {Trait} from '@avocado/entity';
|
|||
|
||||
export default class Roomed extends Trait {
|
||||
|
||||
static behaviorContextTypes() {
|
||||
static behaviorTypes() {
|
||||
return {
|
||||
detachFromRoom: {
|
||||
type: 'void',
|
||||
|
|
Loading…
Reference in New Issue
Block a user