blueshell
Version:
A Behavior Tree implementation in modern Javascript
202 lines • 6.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Condition = exports.Action = exports.Base = void 0;
/**
* Created by josh on 1/10/16.
*/
const uuid_1 = require("uuid");
const models_1 = require("../models");
const utils_1 = require("../utils");
/**
* Base class of all Nodes.
* @author Joshua Chaitin-Pollak
*/
class Base {
static registerTreePublisher(publisher) {
Base.treePublisher = publisher;
}
static registerNodeForDebug(node) {
utils_1.NodeManager.getInstance().addNode(node);
}
static unregisterNodeForDebug(node) {
utils_1.NodeManager.getInstance().removeNode(node);
}
/**
* @constructor
* @param name The name of the Node. If no name is given, the name of the Class will be used.
*/
constructor(name = '') {
this.name = name;
this._id = '';
if (!this.name) {
this.name = this.constructor.name;
}
this._parent = '';
}
/**
* Handles the Event, and invokes `onEvent(state, event)`
* @param state The state when the event occured.
* @param event The event to handle.
* @protected
*/
handleEvent(state, event) {
this._beforeEvent(state, event);
const passed = this.precondition(state, event);
if (!passed) {
return models_1.rc.FAILURE;
}
try {
Base.treePublisher.publishResult(state, event, false);
const result = this.onEvent(state, event);
return this._afterEvent(result, state, event);
}
catch (err) {
state.errorReason = err;
if (this.getDebug(state)) {
console.error('Error: ', err.stack); // eslint-disable-line no-console
}
return models_1.rc.ERROR;
}
}
/**
* Return an empty object
* @ignore
* @param state
* @param event
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_beforeEvent(state, event) {
const pStorage = this._privateStorage(state);
const nodeStorage = this.getNodeStorage(state);
// If this is the root node, increment the event counter
if (!this._parent) {
pStorage.eventCounter = ++pStorage.eventCounter || 1;
}
// Reset the lastResult unless it was previously RUNNING and we're not a parent
if (nodeStorage.lastResult !== models_1.rc.RUNNING || (0, models_1.isParentNode)(this)) {
nodeStorage.lastResult = '';
}
// Record the last event we've seen
// console.log('%s: incrementing event counter %s, %s',
// this.path, nodeStorage.lastEventSeen, pStorage.eventCounter);
nodeStorage.lastEventSeen = pStorage.eventCounter;
return {};
}
/**
* Logging
* @ignore
* @param res
* @param state
* @param event
*/
_afterEvent(res, state, event) {
if (this.getDebug(state)) {
console.log(this.path, ' => ', event, ' => ', res); // eslint-disable-line no-console
}
const storage = this.getNodeStorage(state);
// Cache our results for the next iteration
storage.lastResult = res;
return res;
}
/**
* Return true if this Node should proceed handling the event. false otherwise.
* @param state
* @param event
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
precondition(state, event) {
return true;
}
/**
* Invoked when there is a new event.
* @param state
* @param event
* @return Result. Must be rc.SUCCESS, rc.FAILURE, or rc.RUNNING
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
onEvent(state, event) {
return models_1.rc.SUCCESS;
}
set parent(path) {
this._parent = path;
}
get parent() {
return this._parent;
}
get id() {
if (!this._id) {
this._id = `n${(0, uuid_1.v4)().replace(/\-/g, '')}`;
}
return this._id;
}
get path() {
return (!!this._parent ? `${this._parent}_` : '') + this.name;
}
/**
* Returns storage unique to this Node, keyed on the Node's path.
* @param state
*/
getNodeStorage(state) {
const path = this.path;
const blueshell = this._privateStorage(state);
blueshell[path] = blueshell[path] || {};
return blueshell[path];
}
/**
* Resets the storage unique to this Node, via the Node's path.
* If this node is a parent, then also reset all children.
* @param state
*/
resetNodeStorage(state) {
if ((0, models_1.isParentNode)(this)) {
for (const child of this.getChildren()) {
child.resetNodeStorage(state);
}
}
const path = this.path;
const blueshell = this._privateStorage(state);
blueshell[path] = {};
return blueshell[path];
}
/**
* @ignore
* @param state
*/
_privateStorage(state) {
state.__blueshell = state.__blueshell || {};
return state.__blueshell;
}
getDebug(state) {
return this._privateStorage(state).debug;
}
getTreeEventCounter(state) {
return this._privateStorage(state).eventCounter;
}
/**
* Getter for the previous event seen.
* @param state
*/
getLastEventSeen(state) {
return this.getNodeStorage(state).lastEventSeen;
}
/**
* Getter for the result of the last handled Event.
* @param state
*/
getLastResult(state) {
return this.getNodeStorage(state).lastResult;
}
get symbol() {
return '';
}
}
exports.Base = Base;
exports.Action = Base;
exports.Condition = Base;
// Hard to properly type this since the static can't
// inherit the types from this generic class. This static is
// here because it's difficult to inject this functionality
// into blueshell in the current form, but this is maybe
// marginally better than a global
Base.treePublisher = new utils_1.TreeNonPublisher();
//# sourceMappingURL=Base.js.map