From 13610b390daec30f36526e607b05fe31de09c268 Mon Sep 17 00:00:00 2001 From: Emil Axelsson Date: Fri, 16 Sep 2016 14:53:20 +0200 Subject: [PATCH] distinguish between local and remote scripting --- .../interaction/interactionhandler.h | 3 +- include/openspace/interaction/luaconsole.h | 8 +- .../openspace/network/parallelconnection.h | 57 +++++- include/openspace/scripting/lualibrary.h | 8 +- include/openspace/scripting/scriptengine.h | 15 +- modules/onscreengui/src/gui.cpp | 14 +- .../onscreengui/src/guiorigincomponent.cpp | 3 +- modules/onscreengui/src/guitimecomponent.cpp | 3 +- modules/onscreengui/src/renderproperties.cpp | 2 +- scripts/bind_keys.lua | 10 +- scripts/bind_keys_rosetta.lua | 4 +- scripts/common.lua | 18 +- src/engine/openspaceengine.cpp | 16 +- src/interaction/interactionhandler.cpp | 33 ++- src/interaction/interactionhandler_lua.inl | 153 ++++---------- src/interaction/luaconsole.cpp | 48 ++++- src/interaction/luaconsole_lua.inl | 2 +- src/network/networkengine.cpp | 2 +- src/network/parallelconnection.cpp | 193 ++++++++++++------ src/rendering/renderengine.cpp | 48 ++++- src/rendering/screenspacerenderable.cpp | 2 +- src/scene/scene.cpp | 1 - src/scripting/scriptengine.cpp | 37 ++-- src/util/time.cpp | 12 +- 24 files changed, 419 insertions(+), 273 deletions(-) diff --git a/include/openspace/interaction/interactionhandler.h b/include/openspace/interaction/interactionhandler.h index 61be7b0847..18170bcab2 100644 --- a/include/openspace/interaction/interactionhandler.h +++ b/include/openspace/interaction/interactionhandler.h @@ -67,6 +67,7 @@ public: void addKeyframe(const network::datamessagestructures::CameraKeyframe &kf); void clearKeyframes(); + void bindKeyLocal(Key key, KeyModifier modifier, std::string lua); void bindKey(Key key, KeyModifier modifier, std::string lua); void lockControls(); void unlockControls(); @@ -109,7 +110,7 @@ private: bool _cameraUpdatedFromScript = false; - std::multimap _keyLua; + std::multimap> _keyLua; std::unique_ptr _inputState; Camera* _camera; diff --git a/include/openspace/interaction/luaconsole.h b/include/openspace/interaction/luaconsole.h index 9273a717a7..f4be6586b4 100644 --- a/include/openspace/interaction/luaconsole.h +++ b/include/openspace/interaction/luaconsole.h @@ -26,6 +26,7 @@ #define LUACONSOLE_H #include +#include #include @@ -50,12 +51,16 @@ public: bool isVisible() const; void setVisible(bool visible); - void toggleVisibility(); + bool isRemoteScripting() const; + void setRemoteScripting(bool remoteScripting); + + void toggleMode(); static scripting::LuaLibrary luaLibrary(); private: + void parallelConnectionChanged(const network::Status& status); void addToCommand(std::string c); std::string UnicodeToUTF8(unsigned int codepoint); @@ -73,6 +78,7 @@ private: } _autoCompleteInfo; bool _isVisible; + bool _remoteScripting; }; } // namespace openspace diff --git a/include/openspace/network/parallelconnection.h b/include/openspace/network/parallelconnection.h index fe8d01f2f5..3731401869 100644 --- a/include/openspace/network/parallelconnection.h +++ b/include/openspace/network/parallelconnection.h @@ -33,6 +33,9 @@ //glm includes #include +//ghoul includes +#include + //std includes #include #include @@ -59,16 +62,24 @@ typedef int _SOCKET; #include #endif -namespace openspace{ +namespace openspace { - namespace network{ + namespace network { + enum class Status : uint32_t { + Disconnected = 0, + ClientWithoutHost, + ClientWithHost, + Host + }; + enum class MessageType : uint32_t { Authentication = 0, Data, - HostInformation, + ConnectionStatus, HostshipRequest, - HostshipResignation + HostshipResignation, + NConnections }; struct Message { @@ -106,9 +117,13 @@ namespace openspace{ void setAddress(const std::string &address); void setName(const std::string& name); - + + bool isAuthenticated(); + bool isHost(); - + + const std::string& hostName(); + void requestHostship(const std::string &password); void resignHostship(); @@ -133,7 +148,15 @@ namespace openspace{ * interaction */ static scripting::LuaLibrary luaLibrary(); + + Status status(); + + size_t nConnections(); + + std::shared_ptr> connectionEvent(); + + protected: private: @@ -162,7 +185,10 @@ namespace openspace{ void dataMessageReceived(const std::vector& messageContent); - void hostInfoMessageReceived(const std::vector& messageContent); + void connectionStatusMessageReceived(const std::vector& messageContent); + + void nConnectionsMessageReceived(const std::vector& messageContent); + void broadcast(); @@ -171,26 +197,37 @@ namespace openspace{ void sendFunc(); void threadManagement(); + + void setStatus(Status status); + + void setHostName(const std::string& hostName); + + void setNConnections(size_t nConnections); - std::string scriptFromPropertyAndValue(const std::string property, const std::string value); + //std::string scriptFromPropertyAndValue(const std::string property, const std::string value); uint32_t _passCode; std::string _port; std::string _address; std::string _name; + _SOCKET _clientSocket; std::unique_ptr _connectionThread; std::unique_ptr _broadcastThread; std::unique_ptr _sendThread; std::unique_ptr _listenThread; std::unique_ptr _handlerThread; - std::atomic _isHost; + std::atomic _isConnected; std::atomic _isRunning; std::atomic _tryConnect; std::atomic _disconnect; std::atomic _initializationTimejumpRequired; + std::atomic _nConnections; + std::atomic _status; + std::string _hostName; + std::condition_variable _disconnectCondition; std::mutex _disconnectMutex; @@ -206,6 +243,8 @@ namespace openspace{ std::atomic _latestTimeKeyframeValid; std::map _currentState; std::mutex _currentStateMutex; + + std::shared_ptr> _connectionEvent; }; } // namespace network diff --git a/include/openspace/scripting/lualibrary.h b/include/openspace/scripting/lualibrary.h index 6787575a84..36f4c05d1d 100644 --- a/include/openspace/scripting/lualibrary.h +++ b/include/openspace/scripting/lualibrary.h @@ -37,9 +37,8 @@ namespace scripting { struct LuaLibrary { /** * This structure represents a Lua function with its #name, #function pointer - * #argumentText describing the arguments this function takes, the #helpText - * describing the function, and whether it should be shared in a parallel - * connection (#parallelShared) + * #argumentText describing the arguments this function takes, and the the #helpText + * describing the function. */ struct Function { /// The name of the function @@ -50,9 +49,6 @@ struct LuaLibrary { std::string argumentText; /// A help text describing what the function does/ std::string helpText; - /// If true, this function will be shared with other parallel - /// connections - bool parallelShared; }; /// The name of the library std::string name; diff --git a/include/openspace/scripting/scriptengine.h b/include/openspace/scripting/scriptengine.h index cb7797e181..624569d027 100644 --- a/include/openspace/scripting/scriptengine.h +++ b/include/openspace/scripting/scriptengine.h @@ -49,6 +49,7 @@ namespace scripting { */ class ScriptEngine { public: + using RemoteScripting = ghoul::Boolean; /** * Initializes the internal Lua state and registers a common set of library functions * \throw LuaRuntimeException If the creation of the new Lua state fails @@ -81,7 +82,7 @@ public: void preSynchronization(); - void queueScript(const std::string &script); + void queueScript(const std::string &script, RemoteScripting remoteScripting); void setLogFile(const std::string& filename, const std::string& type); @@ -90,9 +91,9 @@ public: std::vector allLuaFunctions() const; //parallel functions - bool parseLibraryAndFunctionNames(std::string &library, std::string &function, const std::string &script); - bool shouldScriptBeSent(const std::string &library, const std::string &function); - void cacheScript(const std::string &library, const std::string &function, const std::string &script); + //bool parseLibraryAndFunctionNames(std::string &library, std::string &function, const std::string &script); + //bool shouldScriptBeSent(const std::string &library, const std::string &function); + //void cacheScript(const std::string &library, const std::string &function, const std::string &script); private: @@ -109,13 +110,13 @@ private: //sync variables std::mutex _mutex; - std::vector _queuedScripts; + std::vector> _queuedScripts; std::vector _receivedScripts; std::string _currentSyncedScript; //parallel variables - std::map> _cachedScripts; - std::mutex _cachedScriptsMutex; + //std::map> _cachedScripts; + //std::mutex _cachedScriptsMutex; //logging variables bool _logFileExists = false; diff --git a/modules/onscreengui/src/gui.cpp b/modules/onscreengui/src/gui.cpp index 2057c64a38..9955a25cbc 100644 --- a/modules/onscreengui/src/gui.cpp +++ b/modules/onscreengui/src/gui.cpp @@ -155,7 +155,7 @@ void addScreenSpaceRenderable(std::string texturePath) { std::string luaTable = "{Type = 'ScreenSpaceImage', TexturePath = '" + absPath(texturePath) + "' }"; std::string script = "openspace.registerScreenSpaceRenderable(" + luaTable + ");"; - OsEng.scriptEngine().queueScript(script); + OsEng.scriptEngine().queueScript(script, openspace::scripting::ScriptEngine::RemoteScripting::Yes); } } // namespace @@ -453,22 +453,26 @@ void GUI::render() { if (toSun) { OsEng.scriptEngine().queueScript( - "openspace.setPropertyValue('Interaction.coordinateSystem', 'Sun');" + "openspace.setPropertyValue('Interaction.coordinateSystem', 'Sun');", + scripting::ScriptEngine::RemoteScripting::Yes ); } if (toPluto) { OsEng.scriptEngine().queueScript( - "openspace.setPropertyValue('Interaction.coordinateSystem', 'Pluto');" + "openspace.setPropertyValue('Interaction.coordinateSystem', 'Pluto');", + scripting::ScriptEngine::RemoteScripting::Yes ); } if (toJupiter) { OsEng.scriptEngine().queueScript( - "openspace.setPropertyValue('Interaction.coordinateSystem', 'Jupiter');" + "openspace.setPropertyValue('Interaction.coordinateSystem', 'Jupiter');", + scripting::ScriptEngine::RemoteScripting::Yes ); } if (to67P) { OsEng.scriptEngine().queueScript( - "openspace.setPropertyValue('Interaction.coordinateSystem', '67P');" + "openspace.setPropertyValue('Interaction.coordinateSystem', '67P');", + scripting::ScriptEngine::RemoteScripting::Yes ); } diff --git a/modules/onscreengui/src/guiorigincomponent.cpp b/modules/onscreengui/src/guiorigincomponent.cpp index 07bf244161..228e667878 100644 --- a/modules/onscreengui/src/guiorigincomponent.cpp +++ b/modules/onscreengui/src/guiorigincomponent.cpp @@ -65,7 +65,8 @@ void GuiOriginComponent::render() { if (hasChanged) { OsEng.scriptEngine().queueScript( "openspace.setPropertyValue('Interaction.origin', '" + - nodes[currentPosition]->name() + "');" + nodes[currentPosition]->name() + "');", + scripting::ScriptEngine::RemoteScripting::Yes ); } } diff --git a/modules/onscreengui/src/guitimecomponent.cpp b/modules/onscreengui/src/guitimecomponent.cpp index e98c2d5059..c2b1d15d0d 100644 --- a/modules/onscreengui/src/guitimecomponent.cpp +++ b/modules/onscreengui/src/guitimecomponent.cpp @@ -38,7 +38,8 @@ void GuiTimeComponent::render() { bool changed = ImGui::SliderFloat("Delta Time", &deltaTime, -5000.f, 5000.f); if (changed) { OsEng.scriptEngine().queueScript( - "openspace.time.setDeltaTime(" + std::to_string(deltaTime) + ")" + "openspace.time.setDeltaTime(" + std::to_string(deltaTime) + ")", + scripting::ScriptEngine::RemoteScripting::Yes ); } } diff --git a/modules/onscreengui/src/renderproperties.cpp b/modules/onscreengui/src/renderproperties.cpp index 47014d4f11..60d3ce1724 100644 --- a/modules/onscreengui/src/renderproperties.cpp +++ b/modules/onscreengui/src/renderproperties.cpp @@ -47,7 +47,7 @@ void renderTooltip(Property* prop) { void executeScript(const std::string& id, const std::string& value) { std::string script = "openspace.setPropertyValueSingle('" + id + "', " + value + ");"; - OsEng.scriptEngine().queueScript(script); + OsEng.scriptEngine().queueScript(script, scripting::ScriptEngine::RemoteScripting::Yes); } void renderBoolProperty(Property* prop, const std::string& ownerName) { diff --git a/scripts/bind_keys.lua b/scripts/bind_keys.lua index 3dba610ecf..46571003ac 100644 --- a/scripts/bind_keys.lua +++ b/scripts/bind_keys.lua @@ -15,10 +15,14 @@ openspace.bindKey("q", helper.renderable.toggle('SunMarker')) openspace.bindKey("e", helper.renderable.toggle('EarthMarker')) openspace.bindKey("x", helper.renderable.toggle('Constellation Bounds')) -openspace.bindKey("c", "openspace.parallel.setAddress('130.236.142.51');openspace.parallel.setPassword('newhorizons-20150714');openspace.parallel.connect();") +--openspace.bindKey("c", "openspace.parallel.setAddress('130.236.142.51');openspace.parallel.setPassword('newhorizons-20150714');openspace.parallel.connect();") -openspace.bindKey("h", "openspace.iswa.setBaseUrl('https://iswa-demo-server.herokuapp.com/')"); +--openspace.bindKey("h", "openspace.iswa.setBaseUrl('https://iswa-demo-server.herokuapp.com/')"); openspace.bindKey("g", "openspace.iswa.setBaseUrl('http://128.183.168.116:3000/')"); openspace.bindKey("l", "openspace.iswa.setBaseUrl('http://localhost:3000/')"); -openspace.bindKey("v", "openspace.time.setTime('2015-03-15T02:00:00.00')"); \ No newline at end of file +openspace.bindKey("v", "openspace.time.setTime('2015-03-15T02:00:00.00')"); + +openspace.bindKeyLocal("h", "openspace.parallel.setAddress('127.0.0.2');openspace.parallel.setPort('25001');openspace.parallel.setPassword('test');openspace.parallel.connect();openspace.parallel.requestHostship('test');") +openspace.bindKeyLocal("c", "openspace.parallel.setAddress('127.0.0.3');openspace.parallel.setPort('25001');openspace.parallel.setPassword('test');openspace.parallel.connect();") +openspace.bindKeyLocal("d", "openspace.parallel.setAddress('127.0.0.4');openspace.parallel.setPort('25001');openspace.parallel.setPassword('test');openspace.parallel.connect();") \ No newline at end of file diff --git a/scripts/bind_keys_rosetta.lua b/scripts/bind_keys_rosetta.lua index 47bb50541c..d880520069 100644 --- a/scripts/bind_keys_rosetta.lua +++ b/scripts/bind_keys_rosetta.lua @@ -17,10 +17,12 @@ openspace.bindKey("s", "openspace.setPropertyValue('Interaction.origin', 'Rosett -- openspace.bindKey("F5", "openspace.setPropertyValue('Interaction.coordinateSystem', 'Sun'); openspace.printInfo('Changing Viewpoint to Sun');"); openspace.bindKey("F6", "openspace.setPropertyValue('Interaction.coordinateSystem', '67P'); openspace.printInfo('Changing Viewpoint to 67P');"); +openspace.bindKey("F7", "openspace.time.setTime('2014-08-15T03:05:18.101')"); openspace.bindKey("F8", "openspace.setPropertyValue('67P.renderable.ProjectionComponent.clearAllProjections', true);"); openspace.bindKey("i", helper.renderable.toggle('ImagePlaneRosetta')) openspace.bindKey("q", helper.renderable.toggle('SunMarker')) openspace.bindKey("e", helper.renderable.toggle('EarthMarker')) -openspace.bindKey("c", "openspace.parallel.setAddress('130.236.142.51');openspace.parallel.setPassword('newhorizons-20150714');openspace.parallel.connect();") +openspace.bindKeyLocal("h", "openspace.parallel.setAddress('127.0.0.1');openspace.parallel.setPort('25001');openspace.parallel.setPassword('test');openspace.parallel.connect();openspace.parallel.requestHostship('test');") +openspace.bindKeyLocal("c", "openspace.parallel.setAddress('127.0.0.1');openspace.parallel.setPort('25001');openspace.parallel.setPassword('test');openspace.parallel.connect();") diff --git a/scripts/common.lua b/scripts/common.lua index f346422dbc..617022b16f 100644 --- a/scripts/common.lua +++ b/scripts/common.lua @@ -7,24 +7,24 @@ helper.property = {} -- Function that sets the most common key bindings that are common to most (all?) -- scenes helper.setCommonKeys = function() - openspace.bindKey("F1", "openspace.gui.toggle()") - openspace.bindKey("F2", "openspace.setPerformanceMeasurement(true)") - openspace.bindKey("F3", "openspace.setPerformanceMeasurement(false)") + openspace.bindKeyLocal("F1", "openspace.gui.toggle()") + openspace.bindKeyLocal("F2", "openspace.setPerformanceMeasurement(true)") + openspace.bindKeyLocal("F3", "openspace.setPerformanceMeasurement(false)") - openspace.bindKey("t", "openspace.toggleFrametimeType(1)") - openspace.bindKey("Shift+t", "openspace.toggleFrametimeType(0)") + openspace.bindKeyLocal("t", "openspace.toggleFrametimeType(1)") + openspace.bindKeyLocal("Shift+t", "openspace.toggleFrametimeType(0)") - openspace.bindKey("ESC", "openspace.toggleShutdown()") + openspace.bindKeyLocal("ESC", "openspace.toggleShutdown()") - openspace.bindKey("PRINT_SCREEN", "openspace.takeScreenshot()") + openspace.bindKeyLocal("PRINT_SCREEN", "openspace.takeScreenshot()") openspace.bindKey("SPACE", "openspace.time.togglePause()") openspace.bindKey("COMMA", "openspace.setRenderer('Framebuffer');") openspace.bindKey("PERIOD", "openspace.setRenderer('ABuffer');") - openspace.bindKey("f", helper.property.invert('Interaction.rotationalFriction')) - openspace.bindKey("Shift+f", helper.property.invert('Interaction.zoomFriction')) + openspace.bindKeyLocal("f", helper.property.invert('Interaction.rotationalFriction')) + openspace.bindKeyLocal("Shift+f", helper.property.invert('Interaction.zoomFriction')) openspace.bindKey("w", "openspace.toggleFade(3)") end diff --git a/src/engine/openspaceengine.cpp b/src/engine/openspaceengine.cpp index c6c58c861e..509b8248b5 100644 --- a/src/engine/openspaceengine.cpp +++ b/src/engine/openspaceengine.cpp @@ -556,6 +556,7 @@ void OpenSpaceEngine::runScripts(const ghoul::Dictionary& scripts) { //@JK //temporary solution to ensure that startup scripts may be syncrhonized over parallel connection + /* std::ifstream scriptFile; scriptFile.open(absoluteScriptPath.c_str()); std::string line; @@ -569,7 +570,7 @@ void OpenSpaceEngine::runScripts(const ghoul::Dictionary& scripts) { _engine->scriptEngine().cacheScript(lib, func, line); } } - } + }*/ } } @@ -858,14 +859,13 @@ void OpenSpaceEngine::keyboardCallback(Key key, KeyModifier mod, KeyAction actio return; } #endif - - if (key == _console->commandInputButton() && (action == KeyAction::Press || action == KeyAction::Repeat)) - _console->toggleVisibility(); - - if (!_console->isVisible()) { + if (key == _console->commandInputButton()) { + if (action == KeyAction::Press) { + _console->toggleMode(); + } + } else if (!_console->isVisible()) { _interactionHandler->keyboardCallback(key, mod, action); - } - else { + } else { _console->keyboardCallback(key, mod, action); } } diff --git a/src/interaction/interactionhandler.cpp b/src/interaction/interactionhandler.cpp index 9e6399bdcc..6fc4edc188 100644 --- a/src/interaction/interactionhandler.cpp +++ b/src/interaction/interactionhandler.cpp @@ -269,8 +269,10 @@ void InteractionHandler::keyboardCallback(Key key, KeyModifier modifier, KeyActi // iterate over key bindings auto ret = _keyLua.equal_range({ key, modifier }); for (auto it = ret.first; it != ret.second; ++it) { - //OsEng.scriptEngine()->runScript(it->second); - OsEng.scriptEngine().queueScript(it->second); + OsEng.scriptEngine().queueScript(it->second.first, + it->second.second ? + scripting::ScriptEngine::RemoteScripting::Yes : + scripting::ScriptEngine::RemoteScripting::No); } } } @@ -378,12 +380,20 @@ void InteractionHandler::resetKeyBindings() { _keyLua.clear(); } +void InteractionHandler::bindKeyLocal(Key key, KeyModifier modifier, std::string lua) { + _keyLua.insert({ + { key, modifier }, + std::make_pair(lua, false) + }); +} + void InteractionHandler::bindKey(Key key, KeyModifier modifier, std::string lua) { _keyLua.insert({ { key, modifier }, - lua + std::make_pair(lua, true) }); } + void InteractionHandler::writeKeyboardDocumentation(const std::string& type, const std::string& file) { @@ -391,8 +401,14 @@ void InteractionHandler::writeKeyboardDocumentation(const std::string& type, con std::ofstream f(absPath(file)); for (const auto& p : _keyLua) { + std::string remoteScriptingInfo; + bool remoteScripting = p.second.second; + + if (!remoteScripting) { + remoteScriptingInfo = " (LOCAL)"; + } f << std::to_string(p.first) << ": " << - p.second << std::endl; + p.second.first << remoteScriptingInfo << std::endl; } } else { @@ -417,7 +433,14 @@ scripting::LuaLibrary InteractionHandler::luaLibrary() { "bindKey", &luascriptfunctions::bindKey, "string, string", - "Binds a key by name to a lua string command" + "Binds a key by name to a lua string command to execute both locally " + "and to broadcast to clients if this is the host of a parallel session" + }, + { + "bindKeyLocal", + &luascriptfunctions::bindKeyLocal, + "string, string", + "Binds a key by name to a lua string command to execute only locally" }, { "setInteractionMode", diff --git a/src/interaction/interactionhandler_lua.inl b/src/interaction/interactionhandler_lua.inl index 5c52f33131..7fb9388858 100644 --- a/src/interaction/interactionhandler_lua.inl +++ b/src/interaction/interactionhandler_lua.inl @@ -60,7 +60,9 @@ int setOrigin(lua_State* L) { /** * \ingroup LuaScripts * bindKey(): -* Binds a key to Lua command +* Binds a key to Lua command to both execute locally +* and broadcast to all clients if this node is hosting +* a parallel connection. */ int bindKey(lua_State* L) { using ghoul::lua::luaTypeToString; @@ -93,6 +95,42 @@ int bindKey(lua_State* L) { return 0; } +/** +* \ingroup LuaScripts +* bindKey(): +* Binds a key to Lua command to execute only locally +*/ +int bindKeyLocal(lua_State* L) { + using ghoul::lua::luaTypeToString; + + int nArguments = lua_gettop(L); + if (nArguments != 2) + return luaL_error(L, "Expected %i arguments, got %i", 2, nArguments); + + + std::string key = luaL_checkstring(L, -2); + std::string command = luaL_checkstring(L, -1); + + if (command.empty()) + return luaL_error(L, "Command string is empty"); + + openspace::KeyWithModifier iKey = openspace::stringToKey(key); + + if (iKey.key == openspace::Key::Unknown) { + LERRORC("lua.bindKey", "Could not find key '" << key << "'"); + return 0; + } + + OsEng.interactionHandler().bindKeyLocal( + iKey.key, + iKey.modifier, + command + ); + + return 0; +} + + /** * \ingroup LuaScripts * clearKeys(): @@ -176,120 +214,7 @@ int resetCameraDirection(lua_State* L) { OsEng.interactionHandler().resetCameraDirection(); } -#ifdef USE_OLD_INTERACTIONHANDLER -/** -* \ingroup LuaScripts -* dt(bool): -* Get current frame time -*/ -int dt(lua_State* L) { - /* - int nArguments = lua_gettop(L); - if (nArguments != 0) - return luaL_error(L, "Expected %i arguments, got %i", 0, nArguments); - - lua_pushnumber(L,OsEng.interactionHandler().deltaTime()); - */return 1; -} - -/** -* \ingroup LuaScripts -* distance(double, double): -* Change distance to origin -*/ -int distance(lua_State* L) { - /* - int nArguments = lua_gettop(L); - if (nArguments != 2) - return luaL_error(L, "Expected %i arguments, got %i", 2, nArguments); - - double d1 = luaL_checknumber(L, -2); - double d2 = luaL_checknumber(L, -1); - PowerScaledScalar dist(static_cast(d1), static_cast(d2)); - OsEng.interactionHandler().distanceDelta(dist); - */ - return 0; -} - -/** - * \ingroup LuaScripts - * setInteractionSensitivity(double): - * Changes the global interaction sensitivity to the passed value - */ -int setInteractionSensitivity(lua_State* L) { - int nArguments = lua_gettop(L); - if (nArguments != 1) - return luaL_error(L, "Expected %i arguments, got %i", 1, nArguments); - - float sensitivity = static_cast(luaL_checknumber(L, -1)); - //OsEng.interactionHandler().setInteractionSensitivity(sensitivity); - return 0; -} - -/** - * \ingroup LuaScripts - * interactionSensitivity(): - * Returns the current, global interaction sensitivity - */ -int interactionSensitivity(lua_State* L) { - //float sensitivity = OsEng.interactionHandler().interactionSensitivity(); - //lua_pushnumber(L, sensitivity); - return 1; -} - -/** - * \ingroup LuaScripts - * setInvertRoll(bool): - * Determines if the roll movement is inverted - */ -int setInvertRoll(lua_State* L) { - int nArguments = lua_gettop(L); - if (nArguments != 1) - return luaL_error(L, "Expected %i arguments, got %i", 1, nArguments); - - bool invert = lua_toboolean(L, -1) == 1; - //OsEng.interactionHandler().setInvertRoll(invert); - return 0; -} - -/** - * \ingroup LuaScripts - * invertRoll(): - * Returns the current setting for inversion of roll movement - */ -int invertRoll(lua_State* L) { - //bool invert = OsEng.interactionHandler().invertRoll(); - //lua_pushboolean(L, invert); - return 1; -} - -/** - * \ingroup LuaScripts - * setInvertRotation(bool): - * Determines if the rotation movement is inverted - */ -int setInvertRotation(lua_State* L) { - int nArguments = lua_gettop(L); - if (nArguments != 1) - return luaL_error(L, "Expected %i arguments, got %i", 1, nArguments); - - bool invert = lua_toboolean(L, -1) == 1; - //OsEng.interactionHandler().setInvertRotation(invert); - return 0; -} - -/** - * \ingroup LuaScripts - * invertRotation(): - * Returns the current setting for inversion of rotation movement - */ -int invertRotation(lua_State* L) { - //bool invert = OsEng.interactionHandler().invertRotation(); - //lua_pushboolean(L, invert); - return 1; -} -#endif USE_OLD_INTERACTIONHANDLER } // namespace luascriptfunctions } // namespace openspace diff --git a/src/interaction/luaconsole.cpp b/src/interaction/luaconsole.cpp index 1073dfd43e..ee4deb668f 100644 --- a/src/interaction/luaconsole.cpp +++ b/src/interaction/luaconsole.cpp @@ -58,6 +58,7 @@ LuaConsole::LuaConsole() , _filename("") , _autoCompleteInfo({NoAutoComplete, false, ""}) , _isVisible(false) + , _remoteScripting(true) { // _commands.push_back(""); // _activeCommand = _commands.size() - 1; @@ -89,6 +90,13 @@ void LuaConsole::initialize() { } _commands.push_back(""); _activeCommand = _commands.size() - 1; + + OsEng.parallelConnection().connectionEvent()->subscribe("luaConsole", + "statusChanged", [this]() { + network::Status status = OsEng.parallelConnection().status(); + parallelConnectionChanged(status); + }); + } void LuaConsole::deinitialize() { @@ -102,6 +110,8 @@ void LuaConsole::deinitialize() { file.write(s.c_str(), length); } } + + OsEng.parallelConnection().connectionEvent()->unsubscribe("luaConsole"); } void LuaConsole::keyboardCallback(Key key, KeyModifier modifier, KeyAction action) { @@ -169,7 +179,8 @@ void LuaConsole::keyboardCallback(Key key, KeyModifier modifier, KeyAction actio else { std::string cmd = _commands.at(_activeCommand); if (cmd != "") { - OsEng.scriptEngine().queueScript(cmd); + OsEng.scriptEngine().queueScript(cmd, + _remoteScripting ? scripting::ScriptEngine::RemoteScripting::Yes : scripting::ScriptEngine::RemoteScripting::No); // Only add the current command to the history if it hasn't been // executed before. We don't want two of the same commands in a row @@ -309,13 +320,28 @@ void LuaConsole::render() { startY = startY - font_size * 15.0f * 2.0f; const glm::vec4 red(1, 0, 0, 1); + const glm::vec4 lightBlue(0.4, 0.4, 1, 1); const glm::vec4 green(0, 1, 0, 1); const glm::vec4 white(1, 1, 1, 1); std::shared_ptr font = OsEng.fontManager().font("Mono", font_size); using ghoul::fontrendering::RenderFont; - RenderFont(*font, glm::vec2(15.f, startY), red, "$"); + if (_remoteScripting) { + int nClients = OsEng.parallelConnection().nConnections() - 1; + if (nClients == 1) { + RenderFont(*font, glm::vec2(15.f, startY + 20.0f), red, "Broadcasting script to 1 client"); + } else { + RenderFont(*font, glm::vec2(15.f, startY + 20.0f), red, ("Broadcasting script to " + std::to_string(nClients) + " clients").c_str()); + } + RenderFont(*font, glm::vec2(15.f, startY), red, "$"); + } + else { + if (OsEng.parallelConnection().isHost()) { + RenderFont(*font, glm::vec2(15.f, startY + 20.0f), lightBlue, "Local script execution"); + } + RenderFont(*font, glm::vec2(15.f, startY), lightBlue, "$"); + } RenderFont(*font, glm::vec2(15.f + font_size, startY), white, "%s", _commands.at(_activeCommand).c_str()); size_t n = std::count(_commands.at(_activeCommand).begin(), _commands.at(_activeCommand).begin() + _inputPosition, '\n'); @@ -390,10 +416,24 @@ void LuaConsole::setVisible(bool visible) { _isVisible = visible; } -void LuaConsole::toggleVisibility() { - _isVisible = !_isVisible; +void LuaConsole::toggleMode() { + if (_isVisible) { + if (_remoteScripting) { + _remoteScripting = false; + } else { + _isVisible = false; + } + } else { + _remoteScripting = OsEng.parallelConnection().isHost(); + _isVisible = true; + } } +void LuaConsole::parallelConnectionChanged(const network::Status& status) { + _remoteScripting = status == network::Status::Host; +} + + scripting::LuaLibrary LuaConsole::luaLibrary() { return { "console", diff --git a/src/interaction/luaconsole_lua.inl b/src/interaction/luaconsole_lua.inl index 99497bfaeb..97d6ec0b51 100644 --- a/src/interaction/luaconsole_lua.inl +++ b/src/interaction/luaconsole_lua.inl @@ -64,7 +64,7 @@ int toggle(lua_State* L) { if (nArguments != 0) return luaL_error(L, "Expected %i arguments, got %i", 0, nArguments); - OsEng.console().toggleVisibility(); + OsEng.console().toggleMode(); return 0; } diff --git a/src/network/networkengine.cpp b/src/network/networkengine.cpp index 1ddfa24924..b04a70266b 100644 --- a/src/network/networkengine.cpp +++ b/src/network/networkengine.cpp @@ -67,7 +67,7 @@ bool NetworkEngine::handleMessage(const std::string& message) { { std::string script = message.substr(1); //LINFO("Received Lua Script: '" << script << "'"); - OsEng.scriptEngine().queueScript(script); + OsEng.scriptEngine().queueScript(script, scripting::ScriptEngine::RemoteScripting::No); return true; } case MessageTypeExternalControlConnected: diff --git a/src/network/parallelconnection.cpp b/src/network/parallelconnection.cpp index f51f410808..e2c262cdef 100644 --- a/src/network/parallelconnection.cpp +++ b/src/network/parallelconnection.cpp @@ -84,14 +84,16 @@ ParallelConnection::ParallelConnection() , _listenThread(nullptr) , _handlerThread(nullptr) , _isRunning(true) - , _isHost(false) + , _nConnections(0) + , _status(Status::Disconnected) + , _hostName("") , _isConnected(false) , _tryConnect(false) , _disconnect(false) , _latestTimeKeyframeValid(false) , _initializationTimejumpRequired(false) { - //create handler thread + _connectionEvent = std::make_shared>(); _handlerThread = std::make_unique(&ParallelConnection::threadManagement, this); } @@ -174,7 +176,7 @@ void ParallelConnection::disconnect(){ _isConnected.store(false); //tell broadcast thread to stop broadcasting (we're no longer host) - _isHost.store(false); + setStatus(Status::Disconnected); //join connection thread and delete it if(_connectionThread != nullptr){ @@ -320,8 +322,14 @@ void ParallelConnection::establishConnection(addrinfo *info){ //we no longer need to try to establish connection _tryConnect.store(false); + _sendBufferMutex.lock(); + _sendBuffer.clear(); + _sendBufferMutex.unlock(); + //send authentication sendAuthentication(); + } else { + LINFO("Connection attempt failed."); } #ifdef WIN32 @@ -380,8 +388,11 @@ void ParallelConnection::handleMessage(const Message& message) { case MessageType::Data: dataMessageReceived(message.content); break; - case MessageType::HostInformation: - hostInfoMessageReceived(message.content); + case MessageType::ConnectionStatus: + connectionStatusMessageReceived(message.content); + break; + case MessageType::NConnections: + nConnectionsMessageReceived(message.content); break; default: //unknown message type @@ -475,7 +486,7 @@ void ParallelConnection::dataMessageReceived(const std::vector& messageCon uint32_t type = *(reinterpret_cast(messageContent.data())); std::vector buffer(messageContent.begin() + sizeof(uint32_t), messageContent.end()); - switch(type){ + switch(static_cast(type)) { case network::datamessagestructures::Type::CameraData: { network::datamessagestructures::CameraKeyframe kf(buffer); OsEng.interactionHandler().addKeyframe(kf); @@ -522,7 +533,7 @@ void ParallelConnection::dataMessageReceived(const std::vector& messageCon case network::datamessagestructures::Type::ScriptData: { network::datamessagestructures::ScriptMessage sm; sm.deserialize(buffer); - OsEng.scriptEngine().queueScript(sm._script); + OsEng.scriptEngine().queueScript(sm._script, scripting::ScriptEngine::RemoteScripting::No); break; } @@ -562,8 +573,13 @@ void ParallelConnection::sendFunc(){ std::unique_lock unqlock(_sendBufferMutex); _sendCondition.wait(unqlock); + if (_disconnect) { + break; + } + while (!_sendBuffer.empty()) { - const Message& message = _sendBuffer.front(); + Message message = _sendBuffer.front(); + unqlock.unlock(); std::vector header; //insert header into buffer @@ -594,65 +610,79 @@ void ParallelConnection::sendFunc(){ signalDisconnect(); } + unqlock.lock(); _sendBuffer.erase(_sendBuffer.begin()); } - } + } + std::lock_guard sendLock(_sendBufferMutex); + _sendBuffer.clear(); } -void ParallelConnection::hostInfoMessageReceived(const std::vector& message){ - if (message.size() < 1) { - LERROR("Malformed host info message."); +void ParallelConnection::connectionStatusMessageReceived(const std::vector& message) { + if (message.size() < 2 * sizeof(uint32_t)) { + LERROR("Malformed connection status message."); return; } - char hostInfo = message[0]; + size_t pointer = 0; + uint32_t statusIn = *(reinterpret_cast(&message[pointer])); + network::Status status = static_cast(statusIn); + pointer += sizeof(uint32_t); + + size_t hostNameSize = *(reinterpret_cast(&message[pointer])); + pointer += sizeof(uint32_t); + + if (hostNameSize > message.size() - pointer) { + LERROR("Malformed connection status message."); + return; + } + + std::string hostName = ""; + if (hostNameSize > 0) { + hostName = std::string(&message[pointer], hostNameSize); + } + pointer += hostNameSize; - if (hostInfo == 1) { // assigned as host - if (_isHost.load()) { - //we're already host, do nothing (dummy check) - return; - } - _isHost.store(true); + if (status > Status::Host) { + LERROR("Invalid status."); + return; + } - //start broadcasting + if (status == _status) { + //status remains unchanged. + return; + } + + setStatus(status); + setHostName(hostName); + + if (status == Status::Host) { // assigned as host _broadcastThread = std::make_unique(&ParallelConnection::broadcast, this); - } else { // assigned as client - if (!_isHost.load()) { - //we're already a client, do nothing (dummy check) - return; - } - - //stop broadcast loop - _isHost.store(false); - + // delete broadcasting thread + // (the thread is allowed to terminate once the status is set to non-host.) if (_broadcastThread != nullptr) { _broadcastThread->join(); _broadcastThread = nullptr; } OsEng.interactionHandler().clearKeyframes(); - - - //clear buffered any keyframes - - - //request init package from the host - //int size = headerSize(); - //std::vector buffer; - //buffer.reserve(size); - - //write header - //writeHeader(buffer, MessageTypes::InitializationRequest); - - //send message - //std::cout << "host info queue" << std::endl; - //queueMessage(MessageTypes::InitializationRequest, buffer); } - } +void ParallelConnection::nConnectionsMessageReceived(const std::vector& message) { + if (message.size() < sizeof(uint32_t)) { + LERROR("Malformed host info message."); + return; + } + uint32_t nConnections = *(reinterpret_cast(&message[0])); + setNConnections(nConnections); +} + + + + //void ParallelConnection::initializationRequestMessageReceived(const std::vector& message){ /* //get current state as scripts @@ -742,8 +772,10 @@ void ParallelConnection::listenCommunication() { //if enough data was received if (nBytesRead <= 0) { - LERROR("Error " << _ERRNO << " detected in connection when reading header, disconnecting!"); - signalDisconnect(); + if (!_disconnect) { + LERROR("Error " << _ERRNO << " detected in connection when reading header, disconnecting!"); + signalDisconnect(); + } break; } @@ -773,8 +805,10 @@ void ParallelConnection::listenCommunication() { nBytesRead = receiveData(_clientSocket, messageBuffer, messageSize, 0); if (nBytesRead <= 0) { - LERROR("Error " << _ERRNO << " detected in connection when reading message, disconnecting!"); - signalDisconnect(); + if (!_disconnect) { + LERROR("Error " << _ERRNO << " detected in connection when reading message, disconnecting!"); + signalDisconnect(); + } break; } @@ -816,10 +850,7 @@ void ParallelConnection::setName(const std::string& name){ _name = name; } -bool ParallelConnection::isHost(){ - return _isHost.load(); -} - + void ParallelConnection::requestHostship(const std::string &password){ std::vector buffer; uint32_t passcode = hash(password); @@ -863,7 +894,7 @@ bool ParallelConnection::initNetworkAPI(){ } void ParallelConnection::sendScript(const std::string& script) { - if (!_isHost) return; + if (!isHost()) return; network::datamessagestructures::ScriptMessage sm; sm._script = script; @@ -886,7 +917,7 @@ void ParallelConnection::preSynchronization(){ //std::cout << "start." << std::endl; //if we're the host - if(_isHost){ + //if(_isHost){ /* //get current time parameters and create a keyframe network::datamessagestructures::TimeKeyframe tf; @@ -925,8 +956,8 @@ void ParallelConnection::preSynchronization(){ std::cout << "host 7." << std::endl; */ - } - else{ + //} + //else{ /* //if we're not the host and we have a valid keyframe (one that hasnt been used before) if(_latestTimeKeyframeValid.load()){ @@ -951,7 +982,7 @@ void ParallelConnection::preSynchronization(){ } */ - } + //} //std::cout << "stop." << std::endl; } @@ -1012,10 +1043,47 @@ void ParallelConnection::preSynchronization(){ //} +void ParallelConnection::setStatus(Status status) { + if (_status != status) { + _status = status; + _connectionEvent->publish("statusChanged"); + } +} + +Status ParallelConnection::status() { + return _status; +} + +void ParallelConnection::setNConnections(size_t nConnections) { + if (_nConnections != nConnections) { + _nConnections = nConnections; + _connectionEvent->publish("nConnectionsChanged"); + } +} + +size_t ParallelConnection::nConnections() { + return _nConnections; +} + +bool ParallelConnection::isHost() { + return _status == Status::Host; +} + +void ParallelConnection::setHostName(const std::string& hostName) { + if (_hostName != hostName) { + _hostName = hostName; + _connectionEvent->publish("hostNameChanged"); + } +} + +const std::string& ParallelConnection::hostName() { + return _hostName; +} + void ParallelConnection::broadcast(){ //while we're still connected and we're the host - while (_isConnected && _isHost) { + while (_isConnected && isHost()) { //create a keyframe with current position and orientation of camera network::datamessagestructures::CameraKeyframe kf; kf._position = OsEng.interactionHandler().camera()->positionVec3(); @@ -1054,6 +1122,11 @@ uint32_t ParallelConnection::hash(const std::string &val) { return hashVal; }; + + +std::shared_ptr> ParallelConnection::connectionEvent() { + return _connectionEvent; +} scripting::LuaLibrary ParallelConnection::luaLibrary() { return { diff --git a/src/rendering/renderengine.cpp b/src/rendering/renderengine.cpp index 2991099afd..33c6e94179 100644 --- a/src/rendering/renderengine.cpp +++ b/src/rendering/renderengine.cpp @@ -763,23 +763,20 @@ scripting::LuaLibrary RenderEngine::luaLibrary() { "toggleFade", &luascriptfunctions::toggleFade, "number", - "Toggles fading in or out", - true + "Toggles fading in or out" }, { "fadeIn", &luascriptfunctions::fadeIn, "number", - "", - true + "" }, //also temporary @JK { "fadeOut", &luascriptfunctions::fadeOut, "number", - "", - true + "" }, { "registerScreenSpaceRenderable", @@ -1334,6 +1331,45 @@ void RenderEngine::renderInformation() { break; } + network::Status status = OsEng.parallelConnection().status(); + size_t nConnections = OsEng.parallelConnection().nConnections(); + const std::string& hostName = OsEng.parallelConnection().hostName(); + + std::string connectionInfo = ""; + int nClients = nConnections; + if (status == network::Status::Host) { + nClients--; + if (nClients == 1) { + connectionInfo = "Hosting session with 1 client"; + } else { + connectionInfo = "Hosting session with " + std::to_string(nClients) + " clients"; + } + } else if (status == network::Status::ClientWithHost) { + nClients--; + connectionInfo = "Session hosted by '" + hostName + "'"; + } else if (status == network::Status::ClientWithoutHost) { + connectionInfo = "Host is disconnected"; + } + + if (status == network::Status::ClientWithHost || status == network::Status::ClientWithoutHost) { + connectionInfo += "\n"; + if (nClients > 2) { + connectionInfo += "You and " + std::to_string(nClients - 1) + " more clients are tuned in"; + } else if (nClients == 2) { + connectionInfo += "You and " + std::to_string(nClients - 1) + " more client are tuned in"; + } else if (nClients == 1) { + connectionInfo += "You are the only client"; + } + } + + if (connectionInfo != "") { + RenderFontCr(*_fontInfo, + penPosition, + connectionInfo.c_str() + ); + } + + #ifdef OPENSPACE_MODULE_NEWHORIZONS_ENABLED bool hasNewHorizons = scene()->sceneGraphNode("NewHorizons"); double currentTime = Time::ref().currentTime(); diff --git a/src/rendering/screenspacerenderable.cpp b/src/rendering/screenspacerenderable.cpp index dc047dd558..a4b7982b40 100644 --- a/src/rendering/screenspacerenderable.cpp +++ b/src/rendering/screenspacerenderable.cpp @@ -137,7 +137,7 @@ ScreenSpaceRenderable::ScreenSpaceRenderable(const ghoul::Dictionary& dictionary _delete.onChange([this](){ std::string script = "openspace.unregisterScreenSpaceRenderable('" + name() + "');"; - OsEng.scriptEngine().queueScript(script); + OsEng.scriptEngine().queueScript(script, scripting::ScriptEngine::RemoteScripting::Yes); }); } diff --git a/src/scene/scene.cpp b/src/scene/scene.cpp index 9cffc86a82..64b4cc6895 100644 --- a/src/scene/scene.cpp +++ b/src/scene/scene.cpp @@ -467,7 +467,6 @@ scripting::LuaLibrary Scene::luaLibrary() { "Sets a property identified by the URI in " "the first argument. The second argument can be any type, but it has to " "match the type that the property expects.", - true }, { "getPropertyValue", diff --git a/src/scripting/scriptengine.cpp b/src/scripting/scriptengine.cpp index 013706db99..f4a2bd5e0f 100644 --- a/src/scripting/scriptengine.cpp +++ b/src/scripting/scriptengine.cpp @@ -156,16 +156,7 @@ bool ScriptEngine::runScript(const std::string& script) { LERRORC(e.component, e.message); return false; } - - // if we're currently hosting the parallel session, find out if script should be synchronized. - if (OsEng.parallelConnection().isHost()) { - std::string lib, func; - if (parseLibraryAndFunctionNames(lib, func, script) && shouldScriptBeSent(lib, func)){ - OsEng.parallelConnection().sendScript(script); -// cacheScript(lib, func, script); - } - } - + return true; } @@ -194,7 +185,7 @@ bool ScriptEngine::runScriptFile(const std::string& filename) { return true; } -bool ScriptEngine::shouldScriptBeSent(const std::string& library, const std::string& function) { +/*bool ScriptEngine::shouldScriptBeSent(const std::string& library, const std::string& function) { std::set::const_iterator libit; for (libit = _registeredLibraries.cbegin(); libit != _registeredLibraries.cend(); @@ -219,9 +210,9 @@ bool ScriptEngine::shouldScriptBeSent(const std::string& library, const std::str } return false; -} +}*/ -void ScriptEngine::cacheScript(const std::string &library, const std::string &function, const std::string &script){ +/*void ScriptEngine::cacheScript(const std::string &library, const std::string &function, const std::string &script){ _cachedScriptsMutex.lock(); _cachedScripts[library][function] = script; _cachedScriptsMutex.unlock(); @@ -246,8 +237,9 @@ std::vector ScriptEngine::cachedScripts(){ _cachedScriptsMutex.unlock(); return retVal; -} +}*/ +/* bool ScriptEngine::parseLibraryAndFunctionNames(std::string &library, std::string &function, const std::string &script){ //"deconstruct the script to find library and function name @@ -289,7 +281,7 @@ bool ScriptEngine::parseLibraryAndFunctionNames(std::string &library, std::strin //if we found a function all is good return !function.empty(); } - +*/ bool ScriptEngine::isLibraryNameAllowed(lua_State* state, const std::string& name) { bool result = false; lua_getglobal(state, _openspaceLibraryName.c_str()); @@ -665,23 +657,30 @@ void ScriptEngine::preSynchronization() { _mutex.lock(); if (!_queuedScripts.empty()){ - _currentSyncedScript = _queuedScripts.back(); - _queuedScripts.pop_back(); + _currentSyncedScript = _queuedScripts.back().first; + bool remoteScripting = _queuedScripts.back().second; + //Not really a received script but the master also needs to run the script... _receivedScripts.push_back(_currentSyncedScript); + _queuedScripts.pop_back(); + + if (OsEng.parallelConnection().isHost() && remoteScripting) { + OsEng.parallelConnection().sendScript(_currentSyncedScript); + } + } _mutex.unlock(); } -void ScriptEngine::queueScript(const std::string &script){ +void ScriptEngine::queueScript(const std::string &script, ScriptEngine::RemoteScripting remoteScripting){ if (script.empty()) return; _mutex.lock(); - _queuedScripts.insert(_queuedScripts.begin(), script); + _queuedScripts.insert(_queuedScripts.begin(), std::make_pair(script, remoteScripting)); _mutex.unlock(); } diff --git a/src/util/time.cpp b/src/util/time.cpp index 2695202e2e..da403a185a 100644 --- a/src/util/time.cpp +++ b/src/util/time.cpp @@ -249,8 +249,7 @@ scripting::LuaLibrary Time::luaLibrary() { &luascriptfunctions::time_setDeltaTime, "number", "Sets the amount of simulation time that happens " - "in one second of real time", - true + "in one second of real time" }, { "deltaTime", @@ -263,16 +262,14 @@ scripting::LuaLibrary Time::luaLibrary() { "setPause", &luascriptfunctions::time_setPause, "bool", - "Pauses the simulation time or restores the delta time", - true + "Pauses the simulation time or restores the delta time" }, { "togglePause", &luascriptfunctions::time_togglePause, "", "Toggles the pause function, i.e. temporarily setting the delta time to 0" - " and restoring it afterwards", - true + " and restoring it afterwards" }, { "setTime", @@ -281,8 +278,7 @@ scripting::LuaLibrary Time::luaLibrary() { "Sets the current simulation time to the " "specified value. If the parameter is a number, the value is the number " "of seconds past the J2000 epoch. If it is a string, it has to be a " - "valid ISO 8601 date string (YYYY-MM-DDTHH:MN:SS)", - true + "valid ISO 8601 date string (YYYY-MM-DDTHH:MN:SS)" }, { "currentTime",