/* The MIT License (MIT) Copyright (c) 2014-2020 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md) Virtual World Framework Apache 2.0 license (https://github.com/NikolaySuslov/livecodingspace/blob/master/licenses/LICENSE_VWF.md) */ /// FROM FABRIC.JS /// vwf/model.js is the common implementation of all Virtual World Framework models. Each model /// is part of a federation with other models attached to the simulation that implements part of /// the greater model. Taken together, the models create the entire model system for the /// simulation. /// /// Models are inside of, and directly part of the simulation. They may control the simulation /// and cause immediate change, but they cannot accept external input. The model configuration is /// identical for all participants in a shared world. /// /// A given model might be responsible for a certain subset of nodes in the the simulation, such /// as those representing Flash objects. Or it might implement part of the functionality of any /// node, such as translating 3-D transforms and material properties back and forth to a scene /// manager. Or it might implement functionality that is only active for a short period, such as /// importing a document. /// @module vwf/kernel/model /// @requires vwf/model import { Fabric } from '/core/vwf/fabric.js'; class ModelKernel extends Fabric{ constructor(module) { //console.log("ModelKernel constructor"); super( module, "Model") } factory(){ return this.load( this.module, { // == Module Definition ==================================================================== initialize: function() { this.state.enabled = true; // kernel reentry allowed? this.state.blocked = false; // kernel reentry attempted? }, /// Allow kernel reentry from the drivers. enable: function() { this.state.enabled = true; this.state.blocked = false; }, /// Prevent kernel reentry from the drivers. disable: function() { this.state.enabled = false; this.state.blocked = false; }, /// Indicate if kernel reentry is currently enabled. enabled: function() { return this.state.enabled; }, /// Indicate if kernel reentry is currently disabled. disabled: function() { return ! this.state.enabled; }, /// Indicate if a driver attempted to call back into the kernel while reentry was disabled, /// and clear the *blocked* flag. blocked: function() { var blocked = this.state.blocked; this.state.blocked = false; return blocked; }, /// Invoke a task and record any async actions that it initiates. After the actions have /// completed, execute their callbacks, then call a completion callback. /// /// @param {Function} task /// The task to execute and monitor for async actions. `task` is invoked with no /// arguments. /// @param {Function} callback /// Invoked after the async actions have completed. /// @param {Object} [that] /// The `this` value for the `task` and `callback` functions. capturingAsyncs: function( task, callback, that ) { // Create an array to capture the callbacks and results from async actions. When // `this.state.asyncs` exists, async actions hand off their callbacks to `asyncs.defer`. var asyncs = this.state.asyncs = []; asyncs.defer = defer; asyncs.completed = 0; asyncs.callback = callback; asyncs.that = that; // Invoke the task. task.call( that ); // Detach the array from `this.state.asyncs` to stop capturing async actions. this.state.asyncs = undefined; // If there were no async actions, call the completion callback immediately. if ( asyncs.completed == asyncs.length ) { asyncs.callback.call( asyncs.that ); } /// The `this.state.asyncs` array `defer` method. /// /// Wrap a callback function with a new function that will defer the original callback /// until a collection of actions have completed, then call the deferred callbacks /// followed by a completion callback. function defer( callback /* result */ ) { var self = this; // Save the original callback. The wrapping callback will save the result here when // received. var deferred = { callback: callback /* result */, result: undefined }; this.push( deferred ); // Return a new callback in place of the original. Record the result, then if all // actions have completed, call the original callbacks, then call the completion // function. return function( result ) { deferred.result = result; if ( ++self.completed == self.length ) { // Call the original callbacks. self.forEach( function( deferred ) { deferred.callback && deferred.callback( deferred.result ); } ); // Call the completion callback. if ( self.callback ) { self.callback.call( self.that ); } } } }; }, }, function( modelFunctionName ) { // == Model API ============================================================================ // The kernel bypasses vwf/kernel/model and calls directly into the first driver stage. return undefined; }, function( kernelFunctionName ) { // == Kernel API =========================================================================== switch ( kernelFunctionName ) { // -- Read-write functions ------------------------------------------------------------- // TODO: setState // TODO: getState // TODO: hashState case "createNode": return function( nodeComponent, nodeAnnotation, baseURI, when, callback /* nodeID */ ) { // Interpret `createNode( nodeComponent, when, callback )` as // `createNode( nodeComponent, undefined, undefined, when, callback )` and // `createNode( nodeComponent, nodeAnnotation, when, callback )` as // `createNode( nodeComponent, nodeAnnotation, undefined, when, callback )`. // `nodeAnnotation` was added in 0.6.12, and `baseURI` was added in 0.6.25. if ( typeof baseURI == "function" || baseURI instanceof Function ) { callback = baseURI; when = nodeAnnotation; baseURI = undefined; nodeAnnotation = undefined; } else if ( typeof when == "function" || when instanceof Function ) { callback = when; when = baseURI; baseURI = undefined; } // Make the call. if ( this.state.enabled ) { if ( when === undefined ) { if ( this.state.asyncs ) { callback = this.state.asyncs.defer( callback /* nodeID */ ); } return this.kernel[kernelFunctionName]( nodeComponent, nodeAnnotation, function( nodeID ) { callback && callback( nodeID ); } ); } else { this.kernel.virtualTime.plan( undefined, kernelFunctionName, undefined, [ nodeComponent, nodeAnnotation ], when, callback /* result */ ); } } else { this.state.blocked = true; } }; case "deleteNode": return function( nodeID, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID ); } else { this.kernel.virtualTime.plan( nodeID, kernelFunctionName, undefined, undefined, when, callback /* result */ ); } } else { this.state.blocked = true; } }; // TODO: setNode // TODO: getNode case "createChild": return function( nodeID, childName, childComponent, childURI, when, callback /* childID */ ) { if ( this.state.enabled ) { if ( when === undefined ) { if ( this.state.asyncs ) { callback = this.state.asyncs.defer( callback /* childID */ ); } return this.kernel[kernelFunctionName]( nodeID, childName, childComponent, childURI, function( childID ) { callback && callback( childID ); } ); } else { this.kernel.virtualTime.plan( nodeID, kernelFunctionName, childName, [ childComponent, childURI ], when, callback /* result */ ); } } else { this.state.blocked = true; } }; case "deleteChild": return function( nodeID, childName, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID, childName ); } else { this.kernel.virtualTime.plan( nodeID, kernelFunctionName, childName, undefined, when, callback /* result */ ); } } else { this.state.blocked = true; } }; case "addChild": return function( nodeID, childID, childName, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID, childID, childName ); } else { this.kernel.virtualTime.plan( nodeID, kernelFunctionName, undefined, [ childID, childName ], when, callback /* result */ ); // TODO: swap childID & childName? } } else { this.state.blocked = true; } }; case "removeChild": return function( nodeID, childID, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID, childID ); } else { this.kernel.virtualTime.plan( nodeID, kernelFunctionName, undefined, [ childID ], when, callback /* result */ ); // TODO: swap childID & childName? } } else { this.state.blocked = true; } }; case "setProperties": return function( nodeID, properties, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID, properties ); } else { this.kernel.virtualTime.plan( nodeID, kernelFunctionName, undefined, [ properties ], when, callback /* result */ ); } } else { this.state.blocked = true; } }; case "getProperties": return function( nodeID, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID ); } else { this.kernel.virtualTime.plan( nodeID, kernelFunctionName, undefined, undefined, when, callback /* result */ ); } } else { this.state.blocked = true; } }; case "createProperty": return function( nodeID, propertyName, propertyValue, propertyGet, propertySet, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID, propertyName, propertyValue, propertyGet, propertySet ); } else { this.kernel.virtualTime.plan( nodeID, kernelFunctionName, propertyName, [ propertyValue, propertyGet, propertySet ], when, callback /* result */ ); // TODO: { value: propertyValue, get: propertyGet, set: propertySet } ? -- vwf.receive() needs to parse } } else { this.state.blocked = true; } }; // TODO: deleteProperty case "setProperty": return function( nodeID, propertyName, propertyValue, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID, propertyName, propertyValue ); } else { this.kernel.virtualTime.plan( nodeID, kernelFunctionName, propertyName, [ propertyValue ], when, callback /* result */ ); } } else { this.state.blocked = true; } }; case "getProperty": return function( nodeID, propertyName, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID, propertyName ); } else { this.kernel.virtualTime.plan( nodeID, kernelFunctionName, propertyName, undefined, when, callback /* result */ ); } } else { this.state.blocked = true; } }; case "createMethod": return function( nodeID, methodName, methodParameters, methodBody, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID, methodName, methodParameters, methodBody ); } else { this.kernel.virtualTime.plan( nodeID, kernelFunctionName, methodName, [ methodParameters, methodBody ], when, callback /* result */ ); // TODO: { parameters: methodParameters, body: methodBody } ? -- vwf.receive() needs to parse } } else { this.state.blocked = true; } }; // TODO: deleteMethod case "setMethod": return function( nodeID, methodName, methodHandler, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID, methodName, methodHandler ); } else { this.kernel.virtualTime.plan( nodeID, kernelFunctionName, methodName, [ methodHandler ], when, callback /* result */ ); } } else { this.state.blocked = true; } }; case "getMethod": return function( nodeID, methodName, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID, methodName ); } else { this.kernel.virtualTime.plan( nodeID, kernelFunctionName, methodName, undefined, when, callback /* result */ ); } } else { this.state.blocked = true; } }; case "callMethod": return function( nodeID, methodName, methodParameters, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID, methodName, methodParameters ); } else { this.kernel.virtualTime.plan( nodeID, kernelFunctionName, methodName, [ methodParameters ], when, callback /* result */ ); } } else { this.state.blocked = true; } }; case "createEvent": return function( nodeID, eventName, eventParameters, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID, eventName, eventParameters ); } else { this.kernel.virtualTime.plan( nodeID, kernelFunctionName, eventName, [ eventParameters ], when, callback /* result */ ); } } else { this.state.blocked = true; } }; // TODO: deleteEvent case "addEventListener": return function( nodeID, eventName, eventHandler, eventContextID, eventPhases, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID, eventName, eventHandler, eventContextID, eventPhases ); } else { this.kernel.virtualTime.plan( nodeID, kernelFunctionName, eventName, [ eventHandler, eventContextID, eventPhases ], when, callback /* result */ ); } } else { this.state.blocked = true; } }; case "removeEventListener": return function( nodeID, eventName, eventListenerID, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID, eventName, eventListenerID ); } else { this.kernel.virtualTime.plan( nodeID, kernelFunctionName, eventName, [ eventListenerID ], when, callback /* result */ ); } } else { this.state.blocked = true; } }; case "flushEventListeners": return function( nodeID, eventName, eventContextID, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID, eventName, eventContextID ); } else { this.kernel.virtualTime.plan( nodeID, kernelFunctionName, eventName, [ eventContextID ], when, callback /* result */ ); } } else { this.state.blocked = true; } }; case "fireEvent": return function( nodeID, eventName, eventParameters, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID, eventName, eventParameters ); } else { this.kernel.virtualTime.plan( nodeID, kernelFunctionName, eventName, [ eventParameters ], when, callback /* result */ ); } } else { this.state.blocked = true; } }; case "dispatchEvent": return function( nodeID, eventName, eventParameters, eventNodeParameters, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID, eventName, eventParameters, eventNodeParameters ); } else { this.kernel.virtualTime.plan( nodeID, kernelFunctionName, eventName, [ eventParameters, eventNodeParameters ], when, callback /* result */ ); } } else { this.state.blocked = true; } }; case "execute": return function( nodeID, scriptText, scriptType, when, callback /* result */ ) { if ( this.state.enabled ) { if ( when === undefined ) { if ( this.state.asyncs ) { callback = this.state.asyncs.defer( callback /* result */ ); } return this.kernel[kernelFunctionName]( nodeID, scriptText, scriptType, function( result ) { callback && callback( result ); } ); } else { this.kernel.virtualTime.plan( nodeID, kernelFunctionName, undefined, [ scriptText, scriptType ], when, callback /* result */ ); // TODO: { text: scriptText, type: scriptType } ? -- vwf.receive() needs to parse } } else { this.state.blocked = true; } }; case "random": return function( nodeID, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID ); } else { this.kernel.virtualTime.plan( nodeID, kernelFunctionName, undefined, undefined, when, callback /* result */ ); } } else { this.state.blocked = true; } }; case "seed": return function( nodeID, seed, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID, seed ); } else { this.kernel.virtualTime.plan( nodeID, kernelFunctionName, undefined, [ seed ], when, callback /* result */ ); } } else { this.state.blocked = true; } }; // -- Read-only functions -------------------------------------------------------------- case "time": case "client": case "moniker": case "application": case "intrinsics": case "uri": case "name": case "prototype": case "prototypes": case "behaviors": case "ancestors": case "parent": case "children": case "descendants": case "find": case "test": case "findClients": return function() { return this.kernel[kernelFunctionName].apply( this.kernel, arguments ); }; } } ); } } export { ModelKernel }