UNPKG

owl-bt

Version:

owl-bt is editor for Behavior trees. It has been inspired by Unreal engine behavior trees in a way, that it supports special node items like decorators and services. This makes trees smaller and much more readable.

242 lines (201 loc) 6.59 kB
'use strict'; (function () { class ProjectStore { constructor(_, io, $interpolate, $resource, $rootScope, Toposort) { //this.treePath = $location.search().path; this.isLoaded = false; this.version = 1; this._ = _; this._io = io; this._$interpolate = $interpolate; this._$rootScope = $rootScope; this.Toposort = Toposort; this._projectResource = $resource('api/project?path=:treePath', { treePath: '@treePath' }); } load(treePath) { if (this.socket) { this.socket.disconnect(); this.socket = null; } this.treePath = treePath; this.isLoaded = false; this._currentPromise = null; return this._current(); } ensureLoad() { return this._current(); } getCustomType(typeName) { return this.customTypes[typeName] || { type: 'string' }; } getNodeTypeDesc(name) { this._checkLoaded(); let desc = this.nodeTypeDescs[name]; if (!desc) { return { isInvalid: true, name: name, isComposite: true, icon: 'exclamation-triangle' }; } return desc; } getServiceTypeDesc(name) { this._checkLoaded(); let desc = this.serviceTypeDescs[name]; if (!desc) { return this._getFallbackSubItemDesc(name); } return desc; } getDecoratorTypeDesc(name) { this._checkLoaded(); let desc = this.decoratorTypeDescs[name]; if (!desc) { return this._getFallbackSubItemDesc(name); } return desc; } getNodeDescs() { this._checkLoaded(); return this.nodeTypeDescs; } /** * get sub item descriptors of a given type * @param {string} subItemType - 'service' or 'decorator' * @return {desc} descriptor */ getSubItemDescs(subItemType) { this._checkLoaded(); if (subItemType !== 'service' && subItemType !== 'decorator') { throw new Error(`invalid sub item type ${subItemType}`); } return this[subItemType + 'TypeDescs']; } getServiceDescs() { this._checkLoaded(); return this.serviceTypeDescs; } getDecoratorDescs() { this._checkLoaded(); return this.decoratorTypeDescs; } _checkLoaded() { if (!this.isLoaded) { throw new Error('Project is not loaded'); } } _current() { let _this = this; if (!this._currentPromise) { this._currentPromise = this._projectResource.get({ treePath: this.treePath }).$promise.then(prjData => { _this._compileProject(prjData); this.isLoaded = true; this._startPrjChangeWatch(); }); } return this._currentPromise; } _startPrjChangeWatch() { let _this = this; this.socket = this._io('/prj-watch', { query: `treePath=${this.treePath}` }); this.socket.on('prj-reload', prjDataStr => { _this._compileProject(JSON.parse(prjDataStr)); _this._$rootScope.$apply(() => { _this.updateVersion(); }); }); } _compileProject(prjData) { let nodeTypeDescs = this._.keyBy(prjData.nodes || [], 'name'); let serviceTypeDescs = this._.keyBy(prjData.services || [], 'name'); let decoratorTypeDescs = this._.keyBy(prjData.decorators || [], 'name'); this._compileInheritances(nodeTypeDescs); this._compileInheritances(serviceTypeDescs); this._compileInheritances(decoratorTypeDescs); this.nodeTypeDescs = this._withoutAbstract(nodeTypeDescs); this.serviceTypeDescs = this._withoutAbstract(serviceTypeDescs); this.decoratorTypeDescs = this._withoutAbstract(decoratorTypeDescs); this._compileDescriptions(this.nodeTypeDescs); this._compileDescriptions(this.serviceTypeDescs); this._compileDescriptions(this.decoratorTypeDescs); this.customTypes = prjData.types || {}; } _compileInheritances(typeDescs) { let toposort = new this.Toposort(); this._.forEach(typeDescs, desc => { toposort.add(desc.name, (desc.base && (Array.isArray(desc.base) ? desc.base : [desc.base])) || []); }); for (const typeDescName of toposort.sort().reverse()) { let typeDesc = typeDescs[typeDescName]; if (typeDesc) { if (typeDesc.base) { let bases = Array.isArray(typeDesc.base) ? typeDesc.base : [typeDesc.base]; var newTypeDesc = {}; var props = []; bases.forEach(base => { const baseTypeDesc = typeDescs[base]; if (!baseTypeDesc) { throw new Error(`Missing base type '${base}' for '${typeDesc.name}'`) } newTypeDesc = Object.assign(newTypeDesc, baseTypeDesc, typeDesc, { isAbstract: typeDesc.isAbstract }); if (baseTypeDesc.properties && baseTypeDesc.properties.length) { props = props.concat(baseTypeDesc.properties); } }); if (typeDesc.properties && typeDesc.properties.length) { props = props.concat(typeDesc.properties); } var newTypeDescProps = []; props.forEach(cur => { var index = this._.findIndex(newTypeDescProps, e => e.name === cur.name); if (index >= 0) { newTypeDescProps.splice(index, 1, Object.assign({}, newTypeDescProps[index], cur)); } else { newTypeDescProps.push(cur); } }); newTypeDesc.properties = newTypeDescProps; typeDescs[typeDescName] = newTypeDesc; } } } } _withoutAbstract(typeDesc) { return _.pickBy(typeDesc, typeDesc => !typeDesc.isAbstract); } _compileDescriptions(typeDict) { for (let typeName in typeDict) { if (typeDict.hasOwnProperty(typeName)) { let type = typeDict[typeName]; if (type.description) { type.description = this._$interpolate(type.description); } } } } _getFallbackSubItemDesc(name) { return { isInvalid: true, name: name, icon: 'exclamation-triangle' }; } updateVersion() { if (this.version === Number.MAX_SAFE_INTEGER) { this.version = 1; } else { this.version++; } } } angular.module('editorApp') .service('ProjectStore', ProjectStore); })();