Browse Source

allow to save the state

Nikolay Suslov 7 years ago
parent
commit
8fbf571093
3 changed files with 409 additions and 106 deletions
  1. 232 89
      lib/nodejs/reflector.js
  2. 4 5
      support/client/lib/vwf/view/aframe.js
  3. 173 12
      support/client/lib/vwf/view/editor-new.js

+ 232 - 89
lib/nodejs/reflector.js

@@ -93,6 +93,7 @@ function OnConnection( socket ) {
     if( !global.instances[ namespace ] ) {
         global.instances[ namespace ] = { };
         global.instances[ namespace ].clients = { };
+        global.instances[ namespace ].pendingList = [ ];
         global.instances[ namespace ].start_time = undefined;
         global.instances[ namespace ].pause_time = undefined;
         global.instances[ namespace ].rate = 1.0;
@@ -204,9 +205,15 @@ function OnConnection( socket ) {
 
         //keep track of the timer for this instance
         global.instances[ namespace ].timerID = setInterval( function ( ) {
+            var message = { parameters: [ ], time: global.instances[ namespace ].getTime( ) };
             for ( var i in global.instances[ namespace ].clients ) {
                 var client = global.instances[ namespace ].clients[ i ];
-                client.emit( 'message', { parameters: [ ], time: global.instances[ namespace ].getTime( ) } );
+                if ( ! client.pending ) {
+                    client.emit( 'message', message );
+                }
+            }
+            if ( global.instances[ namespace ].pendingList.pending ) {
+                global.instances[ namespace ].pendingList.push( message );
             }
         }, 50 );
 
@@ -216,123 +223,259 @@ function OnConnection( socket ) {
     global.instances[ namespace ].clients[ socket.id ] = socket;	 
 
     socket.pending = true;
-    socket.pendingList = [ ];
-
-    //Create a child in the application's 'clients.vwf' global to represent this client.
-    var clientMessage = { action: "createChild", parameters: [ "http://vwf.example.com/clients.vwf", socket.id, {} ], time: global.instances[ namespace ].getTime( ) };
-
-    // The time for the setState message should be the time the new client joins, so save that time
-    var setStateTime = global.instances[ namespace ].getTime( );
 
-    //The client is the first, is can just load the application, and mark it not pending
-    if ( Object.keys( global.instances[ namespace ].clients ).length === 1 ) {
-
-        if ( saveObject ) {
-            socket.emit( 'message', { action: "setState", parameters: [saveObject], time: global.instances[ namespace ].getTime( ) } );
+    //Get the descriptor for the `clients.vwf` child.
+    var clientDescriptor = GetClientDescriptor( socket );
+    
+        // The time for the setState message should be the time the new client joins, so save that time
+        var setStateTime = global.instances[ namespace ].getTime( );
+    
+        // If this client is the first, it can just load the application, and mark it not pending
+        if ( Object.keys( global.instances[ namespace ].clients ).length === 1 ) {
+    
+            if ( saveObject ) {
+                socket.emit( 'message', {
+                    action: "setState",
+                    parameters: [ saveObject ],
+                    time: global.instances[ namespace ].getTime( )
+                } );
+            }
+            else {
+                var instance = namespace;
+                //Get the state and load it.
+                //Now the server has a rough idea of what the simulation is
+    
+                socket.emit( 'message', { 
+                    action: "createNode", 
+                    parameters: [ "http://vwf.example.com/clients.vwf" ], 
+                    time: global.instances[ namespace ].getTime( ) 
+                } );
+    
+                socket.emit( 'message', { 
+                    action: "createNode", 
+                    parameters: [
+                        ( processedURL.public_path === "/" ? "" : processedURL.public_path ) + "/" + processedURL.application,
+                        "application"
+                    ],
+                    time: global.instances[ namespace ].getTime( )
+                } );
+            }
+    
+            socket.pending = false;
+    
+            //xapi.logClient( saveObject, loadInfo[ 'application_path' ], loadInfo[ 'save_name' ], namespace, clientDescriptor.properties || {}, true, true );
         }
-        else {
-            var instance = namespace;
-            //Get the state and load it.
-            //Now the server has a rough idea of what the simulation is
-
-            socket.emit( 'message', { 
-                action: "createNode", 
-                parameters: [ "http://vwf.example.com/clients.vwf" ], 
-                time: global.instances[ namespace ].getTime( ) 
-            } );
-
-            socket.emit( 'message', { 
-                action: "createNode", 
-                parameters: [
-                    ( processedURL.public_path === "/" ? "" : processedURL.public_path ) + "/" + processedURL.application,
-                    "application"
-                ],
-                time: global.instances[ namespace ].getTime( )
-            } );
-
-            socket.emit( 'message',  clientMessage );
-
+        else {  //this client is not the first, we need to get the state and mark it pending
+            if ( ! global.instances[ namespace ].pendingList.pending ) {
+                var firstclient = Object.keys( global.instances[ namespace ].clients )[ 0 ];
+                firstclient = global.instances[ namespace ].clients[ firstclient ];
+                firstclient.emit( 'message', {
+                    action: "getState",
+                    respond: true,
+                    time: global.instances[ namespace ].getTime( )
+                } );
+                global.instances[ namespace ].Log( 'GetState from Client', 2 );
+                global.instances[ namespace ].pendingList.pending = true;
+            }
+            socket.pending = true;
+    
         }
-        socket.pending = false;
-    }
-    else {  //this client is not the first, we need to get the state and mark it pending
-        var firstclient = Object.keys( global.instances[ namespace ].clients )[ 0 ];
-        firstclient = global.instances[ namespace ].clients[ firstclient ];
-        firstclient.emit( 'message', { action: "getState", respond: true, time: global.instances[ namespace ].getTime( ) } );
-        global.instances[ namespace ].Log( 'GetState from Client', 2 );
-        socket.pending = true;
-        socket.pendingList.push( clientMessage );
+    
+        //Create a child in the application's 'clients.vwf' global to represent this client.
+        var clientNodeMessage = {
+            action: "createChild",
+            parameters: [ "http://vwf.example.com/clients.vwf", socket.id, clientDescriptor ],
+            time: global.instances[ namespace ].getTime( )
+        };
+    
+        // Send messages to all the existing clients (that are not pending),
+        // telling them to create a new node under the "clients" parent for the new client
         for ( var i in global.instances[ namespace ].clients ) {
             var client = global.instances[ namespace ].clients[ i ];
-            if( client.id != socket.id ) {
-                client.emit ( 'message',  clientMessage );
+            if ( !client.pending ) {
+                client.emit ( 'message',  clientNodeMessage );
             }
         }
-    }
-
-    socket.on( 'message', function ( msg ) {
-
-        //need to add the client identifier to all outgoing messages
-        try {
-            var message = JSON.parse( msg );
-        }
-        catch ( e ) {
-            return;
+        if ( global.instances[ namespace ].pendingList.pending ) {
+            global.instances[ namespace ].pendingList.push( clientNodeMessage );
         }
 
-        message.client = socket.id;
-        message.time = global.instances[ namespace ].getTime( );
 
-        //distribute message to all clients on given instance
-        for ( var i in global.instances[ namespace ].clients ) {
-            var client = global.instances[ namespace ].clients[ i ];
-
-            //if the message was get state, then fire all the pending messages after firing the setState
-            if ( message.action == "getState" ) {
-                global.instances[ namespace ].Log( 'Got State', 2 );
-                var state = message.result;
-                global.instances[ namespace ].Log( state, 2 );
-                client.emit( 'message', { action: "setState", parameters: [ state ], time: setStateTime } );
-                client.pending = false;
-                for ( var j = 0; j < client.pendingList.length; j++ ) {
-                    client.emit( 'message', client.pendingList[ j ] );
-                }
-                client.pendingList = [ ];
-            }
-            else {
-                //just a regular message, so push if the client is pending a load, otherwise just send it.
-                if ( client.pending === true ) {
-                    client.pendingList.push( message );
-                }
-                else {
-                    client.emit( 'message', message );
-                }
-            }
-        }
-    } );
+        socket.on( 'message', function ( msg ) {
+            
+                    //need to add the client identifier to all outgoing messages
+                    try {
+                        var message = JSON.parse( msg );
+                    }
+                    catch ( e ) {
+                        console.error( "Error on socket message: ", e );
+                        return;
+                    }
+            
+                    message.client = socket.id;
+                    message.time = global.instances[ namespace ].getTime( );
+            
+                    if ( message.result === undefined ) {
+            
+                        //distribute message to all clients on given instance
+                        for ( var i in global.instances[ namespace ].clients ) {
+                            var client = global.instances[ namespace ].clients[ i ];
+            
+                            //just a regular message, so push if the client is pending a load, otherwise just send it.
+                            if ( ! client.pending ) {
+                                client.emit( 'message', message );
+                            }
+                        }
+            
+                        if ( global.instances[ namespace ].pendingList.pending ) {
+                            global.instances[ namespace ].pendingList.push( message );
+                        }
+            
+                    } else if ( message.action == "getState" ) {
+            
+                        //distribute message to all clients on given instance
+                        for ( var i in global.instances[ namespace ].clients ) {
+                            var client = global.instances[ namespace ].clients[ i ];
+            
+                            //if the message was get state, then fire all the pending messages after firing the setState
+                            if ( client.pending ) {
+                                global.instances[ namespace ].Log( 'Got State', 2 );
+                                var state = message.result;
+                                global.instances[ namespace ].Log( state, 2 );
+                                client.emit( 'message', { action: "setState", parameters: [ state ], time: setStateTime } );
+                                client.pending = false;
+                                for ( var j = 0; j < global.instances[ namespace ].pendingList.length; j++ ) {
+                                    client.emit( 'message', global.instances[ namespace ].pendingList[ j ] );
+                                }
+                                //xapi.logClient( state, undefined, undefined, namespace, GetClientDescriptor( client ).properties || {}, true, false );
+                            }
+                        }
+            
+                        global.instances[ namespace ].pendingList = [ ];
+            
+                    } else if ( message.action === "execute" ) {
+            
+                        var evaluation = socket.pendingEvaluations && socket.pendingEvaluations.shift();
+            
+                        if ( evaluation ) {
+                            evaluation.resolve( message.result );
+                            clearTimeout( evaluation.timeout );
+                        }
+            
+                    }
+            
+                } );
 
     // When a client disconnects, go ahead and remove the instance data
     socket.on( 'disconnect', function ( ) {
         
         // Remove the disconnecting client
+        var leavingClient = global.instances[ namespace ].clients[ socket.id ];
         global.instances[ namespace ].clients[ socket.id ] = null;  
         delete global.instances[ namespace ].clients[ socket.id ];
-        
+        if ( leavingClient.pendingEvaluations ) {
+            leavingClient.pendingEvaluations.forEach( function( evaluation ) {
+                evaluation.reject( new Error( "connection closed" ) );
+                clearTimeout( evaluation.timeout );
+            } );
+        }
+
         // Notify others of the disconnecting client.  Delete the child representing this client in the application's `clients.vwf` global.
         var clientMessage = { action: "deleteChild", parameters: [ "http://vwf.example.com/clients.vwf", socket.id ], time: global.instances[ namespace ].getTime( ) };
         for ( var i in global.instances[ namespace ].clients ) {
             var client = global.instances[ namespace ].clients[ i ];
-            if( client.id != socket.id ) {
-                client.emit ( 'message',  clientMessage );
+            if ( ! client.pending ) {
+                client.emit ( 'message', clientMessage );
             }
         }
+        if ( global.instances[ namespace ].pendingList.pending ) {
+            global.instances[ namespace ].pendingList.push( clientMessage );
+        }
         
         // If it's the last client, delete the data and the timer
         if ( Object.keys( global.instances[ namespace ].clients ).length == 0 ) {
             clearInterval( global.instances[ namespace ].timerID );
             delete global.instances[ namespace ];
+           // xapi.logClient( undefined, loadInfo[ 'application_path' ], loadInfo[ 'save_name' ], namespace, clientDescriptor.properties || {}, false, true );
+        } else {
+           // xapi.logClient( undefined, loadInfo[ 'application_path' ], loadInfo[ 'save_name' ], namespace, clientDescriptor.properties || {}, false, false );
         }
     } );
 }
 
-exports.OnConnection = OnConnection;
+function Evaluate( namespace, node, expression ) {
+
+    return new Promise( function( resolve, reject ) {
+
+        var firstClientID = Object.keys( global.instances[ namespace ].clients )[ 0 ];
+        var firstClient = global.instances[ namespace ].clients[ firstClientID ];
+
+        if ( firstClient ) {
+            firstClient.pendingEvaluations = firstClient.pendingEvaluations || [];
+            firstClient.pendingEvaluations.push( {
+                resolve: resolve,
+                reject: reject,
+                timeout: setTimeout( function() { reject( new Error( "timeout" ) ) }, 1000 ),
+            } );
+            firstClient.emit( "message", { node: node, action: "execute", parameters: [ expression ], respond: true, time: global.instances[ namespace ].getTime() } );
+        } else {
+            reject( new Error( "no clients are connected" ) );
+        }
+
+    } );
+
+}
+
+/// Get a descriptor for the `clients.vwf` child for a new client. An authenticator may set a
+/// descriptor in the session at `session.vwf.client`. If the authenticator doesn't provide a
+/// descriptor, use an empty node inheriting from `client.vwf`.
+
+function GetClientDescriptor( socket ) {
+
+    // socket.io doesn't provide access to the request and the session, but we do have the cookies.
+    // Create a mock request and run it through the session middleware to recreate the session. This
+    // creates a session object at `mockRequest.session`.
+
+    var mockRequest = {
+        headers: { cookie: socket.handshake.headers.cookie },
+        connection: {},
+        session: {},
+    };
+
+    var mockResponse = {
+        getHeader: function() {},
+        setHeader: function() {},
+    };
+
+    sessionStack.forEach( function( middleware ) {
+        middleware( mockRequest, mockResponse, function() {} );
+    } );
+
+    // Get the descriptor from `vwf.client` in the session.
+
+    var descriptor = ( mockRequest.session.vwf || {} ).client || {};
+
+    // Set the default prototype.
+
+    if ( ! descriptor.extends ) {
+        descriptor.extends = "http://vwf.example.com/client.vwf";
+    }
+
+    return descriptor;
+
+}
+
+/// Middleware stack to parse a cookie session from `req.headers.cookie` into `req.session`.
+
+var sessionStack = [
+    //cookieParser(),
+    //cookieSession( { secret: config.get( 'session.secret' ) } ),
+];
+
+function GetInstances() {
+    return global.instances;
+}
+
+exports.OnConnection = OnConnection;
+exports.Evaluate = Evaluate;
+exports.GetInstances = GetInstances;

+ 4 - 5
support/client/lib/vwf/view/aframe.js

@@ -144,9 +144,8 @@ define(["module", "vwf/view"], function (module, view) {
                
                 if (!self.state.nodes[avatarName]) {
 
-                this.kernel.createChild(nodeID, avatarName, newNode, undefined, undefined);
-                this.kernel.addChild(nodeID, avatarName, avatarName);
-                this.kernel.callMethod(avatarName, "createAvatarBody");
+                vwf_view.kernel.createChild(nodeID, avatarName, newNode);
+                vwf_view.kernel.callMethod(avatarName, "createAvatarBody");
                 }
 
             }
@@ -181,8 +180,8 @@ define(["module", "vwf/view"], function (module, view) {
 
             if ( postion && rotation) {
                 //[postion.x, postion.y, postion.z] //[rotation.x, rotation.y, rotation.z]
-            vwf_view.kernel.setProperty(avatarName, "position", postion);
-            vwf_view.kernel.setProperty(avatarName, "rotation", rotation);
+            vwf_view.kernel.setProperty(avatarName, "position", AFRAME.utils.coordinates.stringify(postion));
+            vwf_view.kernel.setProperty(avatarName, "rotation", AFRAME.utils.coordinates.stringify(rotation));
             }
         }
     }

+ 173 - 12
support/client/lib/vwf/view/editor-new.js

@@ -121,7 +121,7 @@ define([
 
 
 
-            ["drawer", "toolbar", "sideBar", "propWindow", "clientsWindow", "codeEditorWindow", "viewSettings", "viewSceneProps"].forEach(item => {
+            ["drawer", "toolbar", "sideBar", "propWindow", "clientsWindow", "codeEditorWindow", "viewSceneProps"].forEach(item => {
                 let el = document.createElement("div");
                 el.setAttribute("id", item);
                 document.body.appendChild(el);
@@ -214,17 +214,146 @@ define([
                 ]
             }
                 
-         document.querySelector('#' + 'viewSettings').$cell({
-                $cell: true,
-                $type: "div",
-                id: 'viewSettings',
-                //style:'z-index: 10; position: absolute; margin-left: 240px;',
-                class: "settingsDiv mdc-toolbar-fixed-adjust",
-                $init: function(){
-                    this.style.visibility = 'hidden';
-                },
-                $components: [viewSettings]
-            })        
+
+            let  loadSaveSettings = 
+            {
+              $cell: true,
+              $type: "div",
+              _getRoot: function(){
+                var app = window.location.pathname;
+                var pathSplit = app.split('/');
+                if (pathSplit[0] == "") {
+                    pathSplit.shift();
+                }
+                if (pathSplit[pathSplit.length - 1] == "") {
+                    pathSplit.pop();
+                }
+                var instIndex = pathSplit.length - 1;
+                if (pathSplit.length > 2) {
+                    if (pathSplit[pathSplit.length - 2] == "load") {
+                        instIndex = pathSplit.length - 3;
+                    }
+                }
+                if (pathSplit.length > 3) {
+                    if (pathSplit[pathSplit.length - 3] == "load") {
+                        instIndex = pathSplit.length - 4;
+                    }
+                }
+
+                var root = "";
+                for (var i = 0; i < instIndex; i++) {
+                    if (root != "") {
+                        root = root + "/";
+                    }
+                    root = root + pathSplit[i];
+                }
+        
+                if (root.indexOf('.vwf') != -1) root = root.substring(0, root.lastIndexOf('/'));
+
+                return root
+              },
+
+                  class: "propGrid max-width mdc-layout-grid mdc-layout-grid--align-left",
+                  $components:[
+                      {
+                          $cell: true,
+                          $type: "div",
+                          class: "mdc-layout-grid__inner",
+                          $components: [
+                            {
+                                $cell: true,
+                                $type: "div",
+                                class: "mdc-layout-grid__cell mdc-layout-grid__cell--span-12",
+                                $components: [
+                                    {
+                                        class: "mdc-textfield",
+                                        $cell: true,
+                                        $type: "span",
+                                        $components: [
+                                            {
+                                                class: "mdc-textfield__input",
+                                                id: "fileName",
+                                                $cell: true,
+                                                $type: "input",
+                                                type: "text",
+                                                value: ""
+                                              
+                                                
+                                            }]
+    
+                                    }
+                                    
+                                ]
+                            },
+                           
+                              {
+                                  $cell: true,
+                                  $type: "div",
+                                  class: "mdc-layout-grid__cell mdc-layout-grid__cell--span-12",
+                                  $components: [
+                                      {
+                                          $cell: true,
+                                          $type: "button",
+                                          class: "mdc-button mdc-button--raised",
+                                          $text: "Save",
+                                          onclick: function (e) {
+                                              let fileName = document.querySelector('#fileName')
+                                            saveStateAsFile.call(self, fileName.value);
+                                            document.querySelector("#fileName").value = '';
+                                              //document.querySelector('#' + 'viewSettings').style.visibility = 'hidden';
+                                          }
+  
+                                      }
+                                      
+                                  ]
+                              },
+                              {
+                                $cell: true,
+                                $type: "div",
+                                class: "mdc-layout-grid__cell mdc-layout-grid__cell--span-12",
+                                $components: [
+                                    { 
+                                        $type: "div", 
+                                        $text: "Loading" },
+                                    
+                                ]
+                            },
+                              {
+                                $cell: true,
+                                $type: "div",
+                                class: "mdc-layout-grid__cell mdc-layout-grid__cell--span-12",
+                                $components: [
+                                    {
+                                        $cell: true,
+                                        $type: "button",
+                                        class: "mdc-button mdc-button--raised",
+                                        $text: "Load",
+                                        onclick: function (e) {
+                                         
+                                            //document.querySelector('#' + 'viewSettings').style.visibility = 'hidden';
+                                        }
+
+                                    }
+                                    
+                                ]
+                            }
+                          ]
+                      }
+                  ]
+              }
+    
+
+        //  document.querySelector('#' + 'viewSettings').$cell({
+        //         $cell: true,
+        //         $type: "div",
+        //         id: 'viewSettings',
+        //         //style:'z-index: 10; position: absolute; margin-left: 240px;',
+        //         class: "settingsDiv mdc-toolbar-fixed-adjust",
+        //         $init: function(){
+        //             this.style.visibility = 'hidden';
+        //         },
+        //         $components: [viewSettings]
+        //     })        
             
             
             
@@ -1138,6 +1267,35 @@ define([
                                             $text: "Settings"
                                         }]
     
+                                    },
+                                    {
+                                        $cell: true,
+                                        $type: "a",
+                                        class: "mdc-list-item",
+                                        $href: "#",
+                                        onclick: function (e) {
+                                            //self.currentNodeID = m.ID;
+    
+                                            // document.querySelector('#clientsList')._setClientNodes(self.nodes["http://vwf.example.com/clients.vwf"]);
+
+                                            let sideBar = document.querySelector('#sideBar');
+                                            
+                                            sideBar._sideBarComponent = loadSaveSettings;
+                                            //document.querySelector("#fileNameTitle").$text = '';
+
+                                            drawer.open = !drawer.open
+                                            document.querySelector('#sideBar').style.visibility = 'visible';
+                                        },
+                                        $components: [{
+                                            $type: "i",
+                                            class: "material-icons mdc-list-item__start-detail",
+                                            'aria-hidden': "true",
+                                            $text: "save"
+                                        },
+                                        {
+                                            $text: "Load/Save"
+                                        }]
+    
                                     },
                                     {
                                         $cell: true,
@@ -3549,11 +3707,14 @@ define([
     {
         this.logger.info("Saving: " + filename);
 
+        var clients = this.nodes["http://vwf.example.com/clients.vwf"];
+
         if (supportAjaxUploadWithProgress.call(this)) {
             var xhr = new XMLHttpRequest();
 
             // Save State Information
             var state = vwf.getState();
+            state.nodes[0].children = {};
 
             var timestamp = state["queue"].time;
             timestamp = Math.round(timestamp * 1000);