diff options
Diffstat (limited to 'indra')
| -rw-r--r-- | indra/llcommon/llstl.h | 204 | ||||
| -rw-r--r-- | indra/llcorehttp/llwebsocketmgr.cpp | 31 | ||||
| -rw-r--r-- | indra/llcorehttp/llwebsocketmgr.h | 62 | ||||
| -rw-r--r-- | indra/newview/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | indra/newview/llpreviewscript.cpp | 48 | ||||
| -rw-r--r-- | indra/newview/llpreviewscript.h | 11 | ||||
| -rw-r--r-- | indra/newview/llscripteditorws.cpp | 423 | ||||
| -rw-r--r-- | indra/newview/llscripteditorws.h | 141 | ||||
| -rw-r--r-- | indra/newview/llsyntaxid.cpp | 17 | ||||
| -rw-r--r-- | indra/newview/llsyntaxid.h | 22 |
10 files changed, 650 insertions, 311 deletions
diff --git a/indra/llcommon/llstl.h b/indra/llcommon/llstl.h index 7d41c42ba7..0088eeec67 100644 --- a/indra/llcommon/llstl.h +++ b/indra/llcommon/llstl.h @@ -35,6 +35,7 @@ #include <list> #include <set> #include <typeinfo> +#include <boost/iterator/filter_iterator.hpp> #ifdef LL_LINUX // <ND> For strcmp @@ -709,5 +710,208 @@ struct ll_template_cast_impl<DEST, SOURCE> \ } \ } +//----------------------------------------------- +namespace LL +{ + /** + * @brief A range adapter that provides filtered iteration over a container. + * + * filter_range creates a filtered view of an iterator range using a predicate function. + * Only elements that satisfy the predicate will be accessible when iterating through + * the range. This is useful for processing subsets of containers without copying data. + * + * The class uses boost::filter_iterator internally to provide the filtering functionality. + * + * @tparam Predicate A callable object (function, functor, lambda) that takes an element + * from the iterator range and returns true if the element should be + * included in the filtered range. + * @tparam Iterator The iterator type for the underlying container/range. + * + * Example usage: + * @code + * std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + * + * // Create a predicate to filter even numbers + * auto is_even = [](int n) { return n % 2 == 0; }; + * + * // Create filtered range using make_filter helper + * auto even_range = LL::make_filter(is_even, numbers.begin(), numbers.end()); + * + * // Iterate through only even numbers + * for (auto value : even_range) { + * std::cout << value << " "; // Prints: 2 4 6 8 10 + * } + * + * // Or manually construct the filter_range + * LL::filter_range<decltype(is_even), std::vector<int>::iterator> + * manual_range(is_even, numbers.begin(), numbers.end()); + * @endcode + * + * @note This class provides a lightweight view over the original data. + * No copying of elements occurs, making it efficient for large containers. + * @note The predicate is applied during iteration, so complex predicates may + * impact performance for frequently-accessed ranges. + * @note The underlying container must remain valid for the lifetime of the filter_range. + * + * @see make_filter() for a convenient factory function + * @see boost::filter_iterator for the underlying implementation details + */ + template<typename Predicate, typename Iterator> + class filter_range + { + public: + /// The filtered iterator type - combines predicate with base iterator + using filter_iter = boost::filter_iterator<Predicate, Iterator>; + + /// Value type of the filtered elements + using value_type = typename std::iterator_traits<Iterator>::value_type; + + /// Iterator type for range-based for loops and STL algorithms + using iterator = filter_iter; + using const_iterator = filter_iter; + + /** + * @brief Constructs a filter_range with the given predicate and iterator range. + * + * @param pred The predicate function/functor to filter elements. + * Must be callable with signature: bool(const value_type&) + * @param begin Iterator to the beginning of the range to filter + * @param end Iterator to the end of the range to filter + * + * @pre begin and end must form a valid iterator range + * @pre pred must be a valid callable that can be invoked with elements from [begin, end) + */ + filter_range(Predicate pred, Iterator begin, Iterator end) + : begin_(pred, begin, end), end_(pred, end, end) {} + + /** + * @brief Returns an iterator to the first element that satisfies the predicate. + * + * @return filter_iter Iterator pointing to the first filtered element, + * or equal to end() if no elements satisfy the predicate. + */ + filter_iter begin() const { return begin_; } + + /** + * @brief Returns an iterator representing the end of the filtered range. + * + * @return filter_iter Past-the-end iterator for the filtered range. + */ + filter_iter end() const { return end_; } + + /** + * @brief Checks if the filtered range is empty. + * + * @return true if no elements in the range satisfy the predicate, false otherwise. + * + * @note This operation has O(1) complexity as it only compares iterators. + */ + bool empty() const { return begin_ == end_; } + + private: + filter_iter begin_; ///< Iterator to first element satisfying predicate + filter_iter end_; ///< Past-the-end iterator for the filtered range + }; + + /** + * @brief Factory function to create a filter_range with automatic template deduction. + * + * This convenience function eliminates the need to explicitly specify template parameters + * when creating a filter_range. The template parameters are automatically deduced from + * the function arguments. + * + * @tparam Predicate Automatically deduced predicate type + * @tparam Iterator Automatically deduced iterator type + * + * @param pred Predicate function/functor for filtering elements + * @param begin Iterator to the beginning of the range + * @param end Iterator to the end of the range + * + * @return filter_range<Predicate, Iterator> A filter_range object configured with + * the provided predicate and range + * + * Example usage: + * @code + * std::vector<std::string> words = {"hello", "world", "test", "example"}; + * + * // Filter strings longer than 4 characters + * auto long_words = LL::make_filter( + * [](const std::string& s) { return s.length() > 4; }, + * words.begin(), + * words.end() + * ); + * + * // Use with range-based for loop + * for (const auto& word : long_words) { + * std::cout << word << std::endl; // Prints: hello, world, example + * } + * + * // Use with STL algorithms + * auto count = std::distance(long_words.begin(), long_words.end()); + * std::cout << "Found " << count << " long words." << std::endl; + * @endcode + * + * @note This function is preferred over direct construction of filter_range + * for most use cases due to automatic template parameter deduction. + */ + template<typename Predicate, typename Iterator> + filter_range<Predicate, Iterator> make_filter(Predicate pred, Iterator begin, Iterator end) + { + return filter_range<Predicate, Iterator>(pred, begin, end); + } + + /** + * @brief Create a filter_range over an entire container with automatic template deduction. + * + * This convenience function creates a filtered view over an entire container without + * requiring explicit begin() and end() calls. It automatically handles both const and + * non-const containers, preserving constness in the resulting iterator types. + * + * @tparam Predicate Automatically deduced predicate type + * @tparam Container Automatically deduced container type (const or non-const) + * + * @param pred Predicate function/functor for filtering elements + * @param container The container to filter (can be const or non-const) + * + * @return filter_range with appropriate iterator type for the container + * + * Example usage: + * @code + * // Non-const container + * std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + * auto evens = LL::make_filter([](int n) { return n % 2 == 0; }, numbers); + * + * // Const container + * const std::list<std::string> words = {"cat", "elephant", "dog", "hippopotamus"}; + * auto long_words = LL::make_filter([](const std::string& s) { return s.size() > 3; }, words); + * + * // Works with any container that supports begin()/end() + * std::set<double> values = {1.1, 2.2, 3.3, 4.4, 5.5}; + * auto large_values = LL::make_filter([](double d) { return d > 3.0; }, values); + * + * // Use with range-based for loops + * for (const auto& word : long_words) { + * std::cout << word << " "; // Prints: elephant hippopotamus + * } + * + * // Chain with STL algorithms + * auto even_count = std::distance(evens.begin(), evens.end()); + * std::cout << "Found " << even_count << " even numbers." << std::endl; + * @endcode + * + * @note This overload automatically calls begin() and end() on the container, + * making it more convenient than the iterator-based version. + * @note The container must remain valid for the lifetime of the returned filter_range. + * @note Constness of the container is preserved in the iterator type. + */ + template<typename Predicate, typename Container> + filter_range<Predicate, decltype(std::begin(std::declval<Container>()))> + make_filter(Predicate pred, Container&& container) + { + return filter_range<Predicate, decltype(std::begin(std::declval<Container>()))>( + pred, std::begin(container), std::end(container)); + } + +} // namespace LL #endif // LL_LLSTL_H diff --git a/indra/llcorehttp/llwebsocketmgr.cpp b/indra/llcorehttp/llwebsocketmgr.cpp index d44d5d877d..09eb728500 100644 --- a/indra/llcorehttp/llwebsocketmgr.cpp +++ b/indra/llcorehttp/llwebsocketmgr.cpp @@ -485,6 +485,7 @@ bool LLWebsocketMgr::WSServer::start() LL_INFOS("WebSocket") << "WebSocket server thread exiting for: " << mServerName << LL_ENDL; }); + onStarted(); LL_INFOS("WebSocket") << "Started WebSocket server thread: " << mServerName << LL_ENDL; return true; } @@ -517,6 +518,7 @@ void LLWebsocketMgr::WSServer::stop() mServerThread.join(); LL_INFOS("WebSocket") << "WebSocket server thread joined for: " << mServerName << LL_ENDL; } + onStopped(); } bool LLWebsocketMgr::WSServer::isRunning() const @@ -596,6 +598,20 @@ LLWebsocketMgr::WSConnection::ptr_t LLWebsocketMgr::WSServer::getConnection(cons return nullptr; } +LLWebsocketMgr::connection_state_t LLWebsocketMgr::WSServer::getConnectionState(const connection_h& handle) const +{ + websocketpp::lib::error_code ec; + auto con = mImpl->mServer.get_con_from_hdl(handle, ec); + if (ec) + { + LL_WARNS("WebSocket") << mServerName << " failed to get connection state: " << ec.message() << LL_ENDL; + websocketpp::session::state::value state = websocketpp::session::state::closed; + return connection_closed; + } + return static_cast<connection_state_t>(con->get_state()); +} + + void LLWebsocketMgr::WSServer::handleOpenConnection(const connection_h& handle) { WSConnection::ptr_t connection; @@ -709,3 +725,18 @@ void LLWebsocketMgr::WSConnection::closeConnection(U16 code, const std::string& LL_WARNS("WebSocket") << "Failed to close connection through server" << LL_ENDL; } } + +bool LLWebsocketMgr::WSConnection::isConnected() const +{ + if (mOwningServer.expired()) + { + return false; + } + + LLWebsocketMgr::WSServer::ptr_t server = mOwningServer.lock(); + if (!server) + { + return false; + } + return server->getConnectionState(mConnectionHandle) == connection_open; +} diff --git a/indra/llcorehttp/llwebsocketmgr.h b/indra/llcorehttp/llwebsocketmgr.h index 570c8ad6cd..4165b3cecc 100644 --- a/indra/llcorehttp/llwebsocketmgr.h +++ b/indra/llcorehttp/llwebsocketmgr.h @@ -64,6 +64,14 @@ public: using connection_h = websocketpp::connection_hdl; class WSServer; + enum connection_state_t + { // must map to websocketpp::session::state + connection_connecting = 0, + connection_open = 1, + connection_closing = 2, + connection_closed = 3 + }; + class WSConnection { friend class LLWebsocketMgr; @@ -84,8 +92,6 @@ public: virtual ~WSConnection() = default; /** - * @brief Called when the connection is opened - * * Override this method in derived classes to handle connection establishment. * This is called after the WebSocket handshake is complete and the connection * is ready to send/receive messages. @@ -93,8 +99,6 @@ public: virtual void onOpen() {} /** - * @brief Called when the connection is closed - * * Override this method in derived classes to handle connection closure. * This is called when the connection has been terminated, either normally * or due to an error condition. @@ -107,27 +111,6 @@ public: * * Override this method in derived classes to handle incoming messages. * Currently only text messages are supported. - * - * @code - * class MyConnection : public LLWebsocketMgr::WSConnection - * { - * public: - * void onMessage(const std::string& message) override - * { - * // Parse and handle the message - * if (message == "ping") { - * sendMessage("pong"); - * } - * // Process JSON messages - * try { - * LLSD data = LLSDSerialize::fromJSON(message); - * handleStructuredMessage(data); - * } catch (...) { - * LL_WARNS("MyConnection") << "Invalid JSON received" << LL_ENDL; - * } - * } - * }; - * @endcode */ virtual void onMessage(const std::string& message) {} @@ -138,17 +121,6 @@ public: * * Sends a text message to the remote endpoint. The message is queued * asynchronously and may not be sent immediately. - * - * @code - * // Send a simple text message - * connection->sendMessage("Hello, client!"); - * - * // Send JSON data - * LLSD response; - * response["status"] = "ok"; - * response["data"] = "some data"; - * connection->sendMessage(LLSDSerialize::toJSON(response)); - * @endcode */ bool sendMessage(const std::string& message) const; bool sendMessage(const boost::json::value& json) const; @@ -172,22 +144,13 @@ public: * - 1008: Policy violation * - 1009: Message too big * - * @code - * // Normal closure - * connection->closeConnection(); - * - * // Close with specific reason - * connection->closeConnection(1000, "Session ended"); - * - * // Close due to policy violation - * connection->closeConnection(1008, "Authentication failed"); - * @endcode - * * @note After calling this method, no further messages should be sent * @note The onClose() callback will be invoked when the close handshake completes */ void closeConnection(U16 code = 1000, const std::string& reason = std::string()); + bool isConnected() const; + protected: connection_h mConnectionHandle; std::weak_ptr<WSServer> mOwningServer; // Back-reference to the server this connection belongs to @@ -260,6 +223,9 @@ public: WSServer(std::string_view name, U16 port, bool local_only = true); virtual ~WSServer(); + virtual void onStarted() {} + virtual void onStopped() {} + virtual void onConnectionOpened(const WSConnection::ptr_t& connection) { } virtual void onConnectionClosed(const WSConnection::ptr_t& connection) { } @@ -273,6 +239,8 @@ public: void broadcastMessage(const std::string& message); virtual bool update() { return true; } + connection_state_t getConnectionState(const connection_h& handle) const; + protected: virtual WSConnection::ptr_t connectionFactory(WSServer::ptr_t server, connection_h handle); diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index d1527ef578..6c7b4fef81 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -1663,6 +1663,7 @@ set(viewer_APPSETTINGS_FILES app_settings/ignorable_dialogs.xml app_settings/key_bindings.xml app_settings/keywords_lsl_default.xml + app_settings/keywords_lua_default.xml app_settings/logcontrol.xml app_settings/settings.xml app_settings/settings_crash_behavior.xml @@ -1671,6 +1672,7 @@ set(viewer_APPSETTINGS_FILES app_settings/std_bump.ini app_settings/toolbars.xml app_settings/trees.xml + app_settings/types_lua_default.llsd app_settings/viewerart.xml app_settings/message.xml ${CMAKE_SOURCE_DIR}/../scripts/messages/message_template.msg diff --git a/indra/newview/llpreviewscript.cpp b/indra/newview/llpreviewscript.cpp index 3ab2e747ea..f17b2aeed1 100644 --- a/indra/newview/llpreviewscript.cpp +++ b/indra/newview/llpreviewscript.cpp @@ -1553,6 +1553,11 @@ LLScriptEdContainer::~LLScriptEdContainer() delete mLiveLogFile; mLiveLogFile = nullptr; + + if (!mWebSocketServer.expired()) + { + unsubscribeScript(); + } } std::string LLScriptEdContainer::getTmpFileName(const std::string& script_name) const @@ -1675,44 +1680,45 @@ void LLScriptEdContainer::startWebsocketServer() U16 server_port(LLScriptEditorWSServer::DEFAULT_SERVER_PORT); bool server_localhost(true); + // Attempt to find an existing server LLWebsocketMgr& wsmgr = LLWebsocketMgr::instance(); LLScriptEditorWSServer::ptr_t server = std::static_pointer_cast<LLScriptEditorWSServer>(wsmgr.findServerByName(server_name)); if (!server) - { + { // We couldn't find one, so create it server = std::make_shared<LLScriptEditorWSServer>(server_name, server_port, server_localhost); wsmgr.addServer(server); - wsmgr.startServer(server_name); + } + + bool is_running = server->isRunning(); + if (!is_running) + { // Server isn't running, so start it + is_running = wsmgr.startServer(server_name); + } + + if (!is_running && !server->isRunning()) + { // Failed to start the server + LL_WARNS() << "Failed to start script editor websocket server" << LL_ENDL; + return; } std::string script_id_hash_str(getUniqueHash()); - server->associateEditor(getHandle(), script_id_hash_str); + server->subscribeScriptEditor(getHandle(), script_id_hash_str); + mWebSocketServer = server; } } -void LLScriptEdContainer::attachToWebSocket(const std::shared_ptr<LLScriptEditorWSConnection>& connection) +void LLScriptEdContainer::unsubscribeScript() { - mWebSocket = connection; -} - -void LLScriptEdContainer::detachFromWebSocket(bool send_disconnect) -{ - if (mWebSocket) + auto server = mWebSocketServer.lock(); + if (server) { - if (send_disconnect) - { - // TODO: - mWebSocket->sendDisconnect(LLScriptEditorWSConnection::REASON_EDITOR_CLOSED); - mWebSocket->closeConnection(); - } - mWebSocket.reset(); + std::string script_id_hash_str(getUniqueHash()); + server->sendUnsubscribeScriptEditor(script_id_hash_str); + server->unsubscribeEditor(script_id_hash_str); } } -void LLScriptEdContainer::cleanupWebSocket() -{ - mWebSocket.reset(); -} /// --------------------------------------------------------------------------- /// LLPreviewLSL diff --git a/indra/newview/llpreviewscript.h b/indra/newview/llpreviewscript.h index 72d52a115e..8d53c8899c 100644 --- a/indra/newview/llpreviewscript.h +++ b/indra/newview/llpreviewscript.h @@ -54,7 +54,7 @@ class LLScriptEdContainer; class LLFloaterGotoLine; class LLFloaterExperienceProfile; class LLScriptMovedObserver; -class LLScriptEditorWSConnection; +class LLScriptEditorWSServer; class LLLiveLSLFile : public LLLiveFile { @@ -105,6 +105,7 @@ public: void initMenu(); void processKeywords(); void processKeywords(bool luau_language); + LLKeywords& getKeywords() { return mEditor->getKeywords(); } void draw() override; bool postBuild() override; @@ -221,9 +222,9 @@ public: bool handleKeyHere(KEY key, MASK mask); void startWebsocketServer(); - void attachToWebSocket(const std::shared_ptr<LLScriptEditorWSConnection>& connection); - void detachFromWebSocket(bool send_disconnect); - void cleanupWebSocket(); + void unsubscribeScript(); + + LLScriptEdCore* getScriptEdCore() const { return mScriptEd; } protected: std::string getTmpFileName(const std::string& script_name) const; @@ -238,7 +239,7 @@ protected: LLLiveLSLFile* mLiveFile = nullptr; LLLiveLSLFile* mLiveLogFile = nullptr; - std::shared_ptr<LLScriptEditorWSConnection> mWebSocket; + std::weak_ptr<LLScriptEditorWSServer> mWebSocketServer; }; // Used to view and edit an LSL script from your inventory. diff --git a/indra/newview/llscripteditorws.cpp b/indra/newview/llscripteditorws.cpp index 9863130aea..cb67880455 100644 --- a/indra/newview/llscripteditorws.cpp +++ b/indra/newview/llscripteditorws.cpp @@ -24,24 +24,6 @@ * $/LicenseInfo$ */ -/** - * This implementation provides JSON-RPC 2.0 WebSocket communication between - * the Second Life viewer and external script editors. It uses the standard - * JSON-RPC 2.0 protocol without pre-defined script-specific methods, - * allowing for flexible integration approaches. - * - * ## JSON-RPC Integration - * - * The connection provides a clean JSON-RPC 2.0 interface that can be - * extended with script-specific functionality as needed: - * - * ### Server-to-Client (Viewer to Editor): - * - `session.handshake`: Welcome message on connection - * - `session.disconnect`: Notify editor of disconnection - * - * ### Notifications (no response expected): - */ - #include "llviewerprecompiledheaders.h" #include "llscripteditorws.h" #include "llpreviewscript.h" @@ -51,6 +33,7 @@ #include "llerror.h" #include "lluuid.h" #include "llversioninfo.h" +#include "llagent.h" //======================================================================== LLScriptEditorWSServer::LLScriptEditorWSServer(const std::string& name, U16 port, bool local_only) @@ -64,7 +47,7 @@ LLWebsocketMgr::WSConnection::ptr_t LLScriptEditorWSServer::connectionFactory(LL LLWebsocketMgr::connection_h handle) { auto connection = std::make_shared<LLScriptEditorWSConnection>(server, handle); - mActiveConnections.insert(connection); + mActiveConnections[connection->getConnectionID()] = connection; // Call setupConnectionMethods to register any global methods setupConnectionMethods(connection); @@ -72,6 +55,29 @@ LLWebsocketMgr::WSConnection::ptr_t LLScriptEditorWSServer::connectionFactory(LL return connection; } +void LLScriptEditorWSServer::onStarted() +{ + LLSyntaxIdLSL& syntax_id_mgr = LLSyntaxIdLSL::instance(); + wptr_t that(std::static_pointer_cast<LLScriptEditorWSServer>(shared_from_this())); + + mLastSyntaxId = syntax_id_mgr.getSyntaxID(); + mLanguageChangeSignal = syntax_id_mgr.addSyntaxIDCallback( + [that]() + { + auto server = that.lock(); + if (server && server->isRunning()) + { + server->broadcastLangugeChange(); + } + }); +} + +void LLScriptEditorWSServer::onStopped() +{ + mLanguageChangeSignal.disconnect(); + mLastSyntaxId.setNull(); +} + void LLScriptEditorWSServer::onConnectionOpened(const LLWebsocketMgr::WSConnection::ptr_t& connection) { // Call parent class to handle JSON-RPC setup and standard methods @@ -92,35 +98,102 @@ void LLScriptEditorWSServer::onConnectionClosed(const LLWebsocketMgr::WSConnecti auto script_connection = std::dynamic_pointer_cast<LLScriptEditorWSConnection>(connection); if (script_connection) { - mActiveConnections.erase(script_connection); + U32 connection_id = script_connection->getConnectionID(); + unsubscribeConnection(connection_id); + mActiveConnections.erase(connection_id); - LL_INFOS("ScriptEditorWS") << "Removed connection from active connections. Total: " + LL_DEBUGS("ScriptEditorWS") << "Removed connection from active connections. Total: " << mActiveConnections.size() << LL_ENDL; // TODO: When connections reach 0, stop the server aftera a timeout. } } -bool LLScriptEditorWSServer::associateEditor(const LLHandle<LLPanel>& editor_handle, const std::string& script_id) +bool LLScriptEditorWSServer::subscribeScriptEditor(const LLHandle<LLPanel>& editor_handle, const std::string &script_id) { if (!editor_handle.isDead()) { - mScriptEditors[script_id] = editor_handle; + auto it = mSubscriptions.find(script_id); + if (it == mSubscriptions.end()) + { // Don't readd if already subscribed + mSubscriptions.emplace(script_id, EditorSubscription{ editor_handle, LLScriptEditorWSConnection::wptr_t() }); + return false; + } + else + { // Update existing subscription with new editor handle + it->second.mEditorHandle = editor_handle; + } return true; } return false; } -void LLScriptEditorWSServer::dissociateEditor(const std::string& script_id) +void LLScriptEditorWSServer::unsubscribeEditor(const std::string &script_id) +{ + auto it = mSubscriptions.find(script_id); + if (it != mSubscriptions.end()) + { + mSubscriptions.erase(it); + } +} + +void LLScriptEditorWSServer::unsubscribeConnection(U32 connection_id) { - mScriptEditors.erase(script_id); + for (auto it = mSubscriptions.begin(); it != mSubscriptions.end(); ) + { + if (it->second.mConnectionID == connection_id) + { + LL_DEBUGS("ScriptEditorWS") << "Unsubscribing script " << it->first + << " from connection ID " << connection_id << LL_ENDL; + it = mSubscriptions.erase(it); + } + else + { + ++it; + } + } } +LLScriptEditorWSServer::SubscriptionError_t LLScriptEditorWSServer::updateScriptSubscription(const std::string &script_id, U32 connection_id) +{ + auto it = mSubscriptions.find(script_id); + if (it != mSubscriptions.end()) + { + if (it->second.mEditorHandle.isDead()) + { + unsubscribeEditor(script_id); + return SUBSCRIPTION_INVALID_EDITOR; + } + + auto con_it = mActiveConnections.find(connection_id); + if (con_it == mActiveConnections.end()) + { + return SUBSCRIPTION_INTERNAL_ERROR; + } + + if ((it->second.mConnectionID != 0) && !it->second.mConnection.expired() + && it->second.mConnection.lock()->isConnected()) + { + LL_WARNS("ScriptEditorWS") << "Script " << script_id << " is already subscribed on connection ID " << it->second.mConnectionID + << ", cannot subscribe again on connection ID " << connection_id << LL_ENDL; + // In the future we may want to support multiple connections per script. + // That would imply it was open in multiple editors. + return SUBSCRIPTION_ALREADY_SUBSCRIBED; + } + + it->second.mConnectionID = connection_id; + it->second.mConnection = con_it->second; + return SUBSCRIPTION_SUCCESS; + } + return SUBSCRIPTION_INVALID_SUBSCRIPTION; +} + + LLHandle<LLPanel> LLScriptEditorWSServer::findEditorForScript(const std::string& script_id) const { - auto it = mScriptEditors.find(script_id); - if (it != mScriptEditors.end()) + auto it = mSubscriptions.find(script_id); + if (it != mSubscriptions.end()) { - return it->second; + return it->second.mEditorHandle; } return LLHandle<LLPanel>(); } @@ -135,9 +208,9 @@ std::shared_ptr<LLScriptEditorWSConnection> LLScriptEditorWSServer::findConnecti std::set<std::string> LLScriptEditorWSServer::getActiveScripts() const { std::set<std::string> active_scripts; - for (const auto& [script_id, editor_handle] : mScriptEditors) + for (const auto& [script_id, subinfo] : mSubscriptions) { - if (!editor_handle.isDead()) + if (!subinfo.mEditorHandle.isDead()) { active_scripts.insert(script_id); } @@ -145,69 +218,182 @@ std::set<std::string> LLScriptEditorWSServer::getActiveScripts() const return active_scripts; } -void LLScriptEditorWSServer::broadcastScriptUpdate(const std::string& script_id, const std::string& content, const LLSD& metadata) +void LLScriptEditorWSServer::setupConnectionMethods(LLJSONRPCConnection::ptr_t connection) { - LL_DEBUGS("ScriptEditorWS") << "Broadcasting script update for script: " << script_id << LL_ENDL; + // Call parent class to register global JSON-RPC methods + LLJSONRPCServer::setupConnectionMethods(connection); + + // Cast to our specific connection type to access script editor functionality + auto script_connection = std::dynamic_pointer_cast<LLScriptEditorWSConnection>(connection); + if (script_connection) + { + LL_DEBUGS("ScriptEditorWS") << "Setting up script editor connection methods" << LL_ENDL; + wptr_t that(std::static_pointer_cast<LLScriptEditorWSServer>(shared_from_this())); + + U32 connection_id = script_connection->getConnectionID(); + + script_connection->registerMethod("language.syntax.id", + [that](const std::string&, const LLSD&, const LLSD&) -> LLSD + { + auto server = that.lock(); + if (server) + { + return server->handleLanguageIdRequest(); + } + return LLSD(); + }); + script_connection->registerMethod("language.syntax", + [that](const std::string&, const LLSD&, const LLSD& params) + { + auto server = that.lock(); + if (server) + { + return server->handleSyntaxRequest(params); + } + return LLSD(); + }); + script_connection->registerMethod("script.subscribe", + [that, connection_id](const std::string&, const LLSD&, const LLSD& params) -> LLSD + { + auto server = that.lock(); + if (server) + { + return server->handleScriptSubscribe(connection_id, params); + } + return LLSD(); + }); + script_connection->registerMethod("script.unsubscribe", [](const std::string&, const LLSD&, const LLSD& params) -> LLSD + { // this is a notification, no response expected + return LLSD(); + }); + // script_connection->registerMethod("language.syntax", ) + } +} - LLSD params; - params["script_id"] = script_id; - params["content"] = content; - params["timestamp"] = LLDate::now().asString(); +void LLScriptEditorWSServer::broadcastLangugeChange() +{ + LLUUID syntax_id = LLSyntaxIdLSL::instance().getSyntaxID(); - if (!metadata.isUndefined()) + if (syntax_id != mLastSyntaxId) { - params["metadata"] = metadata; + mLastSyntaxId = syntax_id; + LLSD params; + params["id"] = syntax_id; + + if (isRunning()) + { + broadcastNotification("language.syntax.change", params); + } } +} + +LLSD LLScriptEditorWSServer::handleLanguageIdRequest() const +{ + LLSD response; - // Send to all connected editors as a notification - broadcastNotification("script.update", params); + response["id"] = mLastSyntaxId; + return response; } -void LLScriptEditorWSServer::broadcastCompilationResult(const std::string& script_id, bool success, const LLSD& errors) +LLSD LLScriptEditorWSServer::handleSyntaxRequest(const LLSD& params) const { - LL_DEBUGS("ScriptEditorWS") << "Broadcasting compilation result for script: " << script_id - << " (success: " << success << ")" << LL_ENDL; + LLSD response(LLSD::emptyMap()); + std::string category = params["kind"].asString(); - LLSD params; - params["script_id"] = script_id; - params["success"] = success; - params["timestamp"] = LLDate::now().asString(); + response["id"] = mLastSyntaxId; - if (!errors.isUndefined() && errors.isArray()) + if (category == "types.luau") { - params["errors"] = errors; + response["types"] = LLSyntaxLua::instance().getTypesXML(); } + else + { + LLSD syntax = LLSyntaxIdLSL::instance().getKeywordsXML(); - // Send to all connected editors as a notification - broadcastNotification("compilation.result", params); + // TODO: support language definitions and additional modules. + + if (syntax.has(category)) + { + response[category] = syntax[category]; + } + } + return response; } -void LLScriptEditorWSServer::setupConnectionMethods(LLJSONRPCConnection::ptr_t connection) +LLSD LLScriptEditorWSServer::handleScriptSubscribe(U32 connection_id, const LLSD& params) { - // Call parent class to register global JSON-RPC methods - LLJSONRPCServer::setupConnectionMethods(connection); + LLSD response(LLSD::emptyMap()); - // Cast to our specific connection type to access script editor functionality - auto script_connection = std::dynamic_pointer_cast<LLScriptEditorWSConnection>(connection); - if (script_connection) + std::string script_id = params["script_id"].asString(); + std::string script_name = params["script_name"].asString(); + std::string language = params["script_language"].asString(); + + SubscriptionError_t result = updateScriptSubscription(script_id, connection_id); + + response["script_id"] = script_id; + response["success"] = (result == SUBSCRIPTION_SUCCESS); + response["status"] = result; + + LL_WARNS_IF(result != SUBSCRIPTION_SUCCESS, "ScriptEditorWS") + << "Script connect request for script " << script_id << " failed with status " << result << LL_ENDL; + switch (result) { - LL_INFOS("ScriptEditorWS") << "Setting up script editor connection methods" << LL_ENDL; + case SUBSCRIPTION_SUCCESS: + response["message"] = "OK"; + break; + case SUBSCRIPTION_INVALID_EDITOR: + response["message"] = "Invalid editor handle"; + break; + case SUBSCRIPTION_INVALID_SUBSCRIPTION: + response["message"] = "No subscription found for script"; + break; + case SUBSCRIPTION_ALREADY_SUBSCRIBED: + response["message"] = "Script already subscribed"; + break; + case SUBSCRIPTION_INTERNAL_ERROR: + response["message"] = "Internal server error"; + break; + } - // Here derived classes could add script-specific method registrations - // For now, the base LLScriptEditorWSConnection doesn't register any specific methods - // but this provides a hook for future customization + if (result == SUBSCRIPTION_SUCCESS) + { + //TODO: Build an info block for the subscribed script. + //buildScriptSubscriptionInfo(result); + } - // Example of how custom methods could be registered: - // script_connection->registerMethod("script.custom", handler); + return response; +} + +LLSD LLScriptEditorWSServer::handleScriptUnsubscribe(U32 connection_id, const LLSD& params) +{ + std::string script_id = params["script_id"].asString(); + + auto it = mSubscriptions.find(script_id); + if (it != mSubscriptions.end() && (it->second.mConnectionID == connection_id)) + { + unsubscribeEditor(script_id); } + return LLSD(); } -//======================================================================== -LLScriptEdContainer* LLScriptEditorWSConnection::getEditor() const +void LLScriptEditorWSServer::sendUnsubscribeScriptEditor(const std::string& script_id) { - return mEditorPanel.isDead() ? nullptr : dynamic_cast<LLScriptEdContainer*>(mEditorPanel.get()); + auto it = mSubscriptions.find(script_id); + if (it != mSubscriptions.end()) + { + auto connection = it->second.mConnection.lock(); + if (connection) + { + LLSD params; + params["script_id"] = script_id; + connection->notify("script.unsubscribe", params); + } + } } +//======================================================================== +U32 LLScriptEditorWSConnection::sNextConnectionID = 1; + std::shared_ptr<LLScriptEditorWSServer> LLScriptEditorWSConnection::getServer() const { return std::static_pointer_cast<LLScriptEditorWSServer>(mOwningServer.lock()); @@ -220,20 +406,17 @@ void LLScriptEditorWSConnection::onOpen() LL_INFOS("ScriptEditorWS") << "Script editor JSON-RPC connection opened" << LL_ENDL; - // Generate unique editor session ID - mEditorId = LLUUID::generateNewID().asString(); - mEditorReady = false; - - LL_INFOS("ScriptEditorWS") << "Initialized editor session: " << mEditorId << LL_ENDL; - - // Build hello data according to the protocol specification + // Build hello data LLSD handshake; handshake["server_version"] = "1.0.0"; handshake["protocol_version"] = "1.0"; handshake["viewer_name"] = LLVersionInfo::instance().getChannel(); handshake["viewer_version"] = LLVersionInfo::instance().getVersion(); - // Supported languages array + handshake["agent_id"] = gAgent.getID(); + + // handshake["challenge"] = ... TODO: simple challenge, write to a file and have the client echo it back? + LLSD languages = LLSD::emptyArray(); languages.append("lsl"); languages.append("luau"); @@ -243,14 +426,19 @@ void LLScriptEditorWSConnection::onOpen() LLSD features; features["live_sync"] = true; features["compilation"] = true; - features["syntax_highlight"] = true; handshake["features"] = features; - // Send editor.handshake method call to the client and handle response - call("session.handshake", handshake, [this](const LLSD& result, const LLSD& error) { + wptr_t that = shared_from_this(); + + // Send session.handshake method call and the response + call("session.handshake", handshake, [that](const LLSD& result, const LLSD& error) { if (error.isUndefined()) { - handleHandshakeResponse(result); + auto self = that.lock(); + if (self) + { + self->handleHandshakeResponse(result); + } } else { @@ -266,17 +454,7 @@ void LLScriptEditorWSConnection::onClose() { // Call parent class to clean up JSON-RPC infrastructure LLJSONRPCConnection::onClose(); - - LL_INFOS("ScriptEditorWS") << "Script editor JSON-RPC connection closed for session: " - << mEditorId << LL_ENDL; - - cleanupConnection(); - - // Clean up editor-specific state - mEditorId.clear(); - mEditorCapabilities.clear(); - mScriptId.clear(); - mEditorReady = false; + mOwningServer.reset(); // Clean up handshake response data mClientName.clear(); @@ -297,6 +475,8 @@ void LLScriptEditorWSConnection::handleHandshakeResponse(const LLSD& result) mClientVersion = result["client_version"].asString(); mProtocolVersion = result["protocol_version"].asString(); + // TODO: Validate challenge_response if implemented + // Validate protocol compatibility if (mProtocolVersion != "1.0") { @@ -307,7 +487,6 @@ void LLScriptEditorWSConnection::handleHandshakeResponse(const LLSD& result) // Store script information if provided mScriptName = result["script_name"].asString(); mScriptLanguage = result["script_language"].asString(); - mScriptId = result["script_id"].asString(); // Store supported languages for (const auto& lang : llsd::inArray( result["languages"])) @@ -326,67 +505,7 @@ void LLScriptEditorWSConnection::handleHandshakeResponse(const LLSD& result) } } - connectToEditor(mScriptId); - // Mark editor as ready - mEditorReady = true; - - LL_INFOS("ScriptEditorWS") << "Handshake completed successfully for session: " << mEditorId << LL_ENDL; -} - -bool LLScriptEditorWSConnection::connectToEditor(const std::string& script_id) -{ - LLScriptEditorWSServer::ptr_t server = std::dynamic_pointer_cast<LLScriptEditorWSServer>(mOwningServer.lock()); - if (!server) - { - LL_WARNS("ScriptEditorWS") << "Cannot connect to editor - server reference lost" << LL_ENDL; - return false; - } - - mEditorPanel = server->findEditorForScript(script_id); - - LLScriptEdContainer* editor_core = getEditor(); - if (!editor_core) - { - LL_INFOS("ScriptEditorWS") << "Could not find editor: " << script_id << LL_ENDL; - // TODO: Disconnect the client if no editor found - return false; - } - - return true; -} - -void LLScriptEditorWSConnection::cleanupConnection() -{ - LL_INFOS("ScriptEditorWS") << "Cleaning up connection for editor session: " << mEditorId << LL_ENDL; - - LLScriptEditorWSServer::ptr_t server = getServer(); - if (server) - { - server->dissociateEditor(mScriptId); - } - - LLScriptEdContainer* editor_core = getEditor(); - - if (editor_core) - { - editor_core->cleanupWebSocket(); - - // Notify the editor panel of disconnection - //editor_core->onExternalEditorDisconnected(); - } - - mEditorPanel = LLHandle<LLPanel>(); -} - - -void LLScriptEditorWSConnection::sendDisconnect(S32 reason, const std::string& message) -{ - LL_INFOS("ScriptEditorWS") << "Sending disconnect message to editor (reason: " - << reason << ", message: " << message << ")" << LL_ENDL; - - LLSD params; - params["reason"] = reason; - params["message"] = message; + notify("session.ok"); - notify("session.disconnect", params); + LL_INFOS("ScriptEditorWS") << "Handshake completed successfully." << LL_ENDL; } diff --git a/indra/newview/llscripteditorws.h b/indra/newview/llscripteditorws.h index 78c675a1f4..f6e40fad6e 100644 --- a/indra/newview/llscripteditorws.h +++ b/indra/newview/llscripteditorws.h @@ -42,44 +42,30 @@ class LLLiveLSLEditor; class LLScriptEdContainer; class LLScriptEditorWSServer; -/** - * @class LLScriptEditorWSConnection - * @brief JSON-RPC WebSocket connection specialized for external script editor communication - * - * This class handles JSON-RPC 2.0 communication between the Second Life viewer - * and external script editors. It provides a clean base for implementing - * script editor integration using the standard JSON-RPC 2.0 protocol. - * - * ## Usage - * - * @code - * // Create server and let base JSON-RPC handle method registration - * auto server = std::make_shared<LLScriptEditorWSServer>("script_editor_server", 9020); - * - * // Register custom methods as needed - * connection->registerMethod("custom.method", handler); - * @endcode - */ -class LLScriptEditorWSConnection : public LLJSONRPCConnection, - public std::enable_shared_from_this<LLScriptEditorWSConnection> +class LLScriptEditorWSConnection : public LLJSONRPCConnection, public std::enable_shared_from_this<LLScriptEditorWSConnection> { public: + using ptr_t = std::shared_ptr<LLScriptEditorWSConnection>; + using wptr_t = std::weak_ptr<LLScriptEditorWSConnection>; + enum DisconnectReason { - REASON_NORMAL = 0, - REASON_EDITOR_CLOSED = 1, + REASON_NORMAL = 0, + REASON_EDITOR_CLOSED = 1, REASON_PROTOCOL_ERROR = 2, - REASON_TIMEOUT = 3, + REASON_TIMEOUT = 3, REASON_INTERNAL_ERROR = 4 }; - LLScriptEditorWSConnection(const LLWebsocketMgr::WSServer::ptr_t server, - const LLWebsocketMgr::connection_h& handle) - : LLJSONRPCConnection(server, handle) - { } + LLScriptEditorWSConnection(const LLWebsocketMgr::WSServer::ptr_t server, const LLWebsocketMgr::connection_h& handle) : + LLJSONRPCConnection(server, handle) + { + mConnectionID = sNextConnectionID++; + } ~LLScriptEditorWSConnection() override = default; + U32 getConnectionID() const { return mConnectionID; } // Connection lifecycle overrides void onOpen() override; @@ -100,26 +86,21 @@ private: */ void handleHandshakeResponse(const LLSD& result); - bool connectToEditor(const std::string& script_id); - void cleanupConnection(); - - LLScriptEdContainer* getEditor() const; + LLScriptEdContainer* getEditor() const; std::shared_ptr<LLScriptEditorWSServer> getServer() const; - std::string mEditorId; ///< Unique identifier for this editor session - LLSD mEditorCapabilities; ///< Editor capabilities metadata - std::string mScriptId; ///< Unique identifier for the script being edited - bool mEditorReady; ///< Whether editor has completed initialization - LLHandle<LLPanel> mEditorPanel; ///< Handle to the associated LSL editor panel + U32 mConnectionID{ 0 }; ///< Unique identifier for this connection // Client handshake response data - std::string mClientName; ///< Name of the external editor client - std::string mClientVersion; ///< Version of the external editor client - std::string mProtocolVersion; ///< JSON-RPC protocol version supported by client - std::string mScriptName; ///< Name of the script being edited - std::string mScriptLanguage; ///< Programming language of the script (lsl, luau, etc.) - string_set_t mLanguages; ///< Set of supported scripting languages - string_set_t mFeatures; ///< Active client features (live_sync, compilation, etc.) + std::string mClientName; ///< Name of the external editor client + std::string mClientVersion; ///< Version of the external editor client + std::string mProtocolVersion; ///< JSON-RPC protocol version supported by client + std::string mScriptName; ///< Name of the script being edited + std::string mScriptLanguage; ///< Programming language of the script (lsl, luau, etc.) + string_set_t mLanguages; ///< Set of supported scripting languages + string_set_t mFeatures; ///< Active client features (live_sync, compilation, etc.) + + static U32 sNextConnectionID; }; /** @@ -161,21 +142,34 @@ private: class LLScriptEditorWSServer : public LLJSONRPCServer { public: + enum SubscriptionError_t + { + SUBSCRIPTION_SUCCESS = 0, + SUBSCRIPTION_INVALID_EDITOR, + SUBSCRIPTION_INVALID_SUBSCRIPTION, + SUBSCRIPTION_ALREADY_SUBSCRIBED, + SUBSCRIPTION_INTERNAL_ERROR + }; + static constexpr char const* DEFAULT_SERVER_NAME = "script_editor_server"; static constexpr U16 DEFAULT_SERVER_PORT = 9020; using ptr_t = std::shared_ptr<LLScriptEditorWSServer>; + using wptr_t = std::weak_ptr<LLScriptEditorWSServer>; LLScriptEditorWSServer(const std::string& name, U16 port, bool local_only = true); virtual ~LLScriptEditorWSServer() = default; - // Server lifecycle callbacks + void onStarted() override; + void onStopped() override; void onConnectionOpened(const LLWebsocketMgr::WSConnection::ptr_t& connection) override; void onConnectionClosed(const LLWebsocketMgr::WSConnection::ptr_t& connection) override; - bool associateEditor(const LLHandle<LLPanel>& editor_handle, const std::string& script_id); - void dissociateEditor(const std::string& script_id); + bool subscribeScriptEditor(const LLHandle<LLPanel>& editor_handle, const std::string &script_id); + void unsubscribeEditor(const std::string &script_id); + + void sendUnsubscribeScriptEditor(const std::string& script_id); LLHandle<LLPanel> findEditorForScript(const std::string& script_id) const; std::shared_ptr<LLScriptEditorWSConnection> findConnectionForScript(const std::string& script_id); @@ -186,45 +180,38 @@ public: */ std::set<std::string> getActiveScripts() const; - /** - * @brief Send script content to all connected editors for a specific script - * @param script_id The script identifier - * @param content The script content - * @param metadata Optional metadata about the script - */ - void broadcastScriptUpdate(const std::string& script_id, const std::string& content, const LLSD& metadata = LLSD()); - - /** - * @brief Send compilation results to all connected editors for a specific script - * @param script_id The script identifier - * @param success Whether compilation succeeded - * @param errors Array of compilation errors/warnings - */ - void broadcastCompilationResult(const std::string& script_id, bool success, const LLSD& errors = LLSD()); - protected: LLWebsocketMgr::WSConnection::ptr_t connectionFactory(LLWebsocketMgr::WSServer::ptr_t server, LLWebsocketMgr::connection_h handle) override; - /** - * @brief Apply global method handlers to a new connection - * @param connection The connection to configure - * - * Override this method to customize which methods are registered on - * new connections. The base implementation registers all global methods, - * but derived classes can add additional script-specific methods. - */ - virtual void setupConnectionMethods(LLJSONRPCConnection::ptr_t connection) override; + void setupConnectionMethods(LLJSONRPCConnection::ptr_t connection) override; + + void broadcastLangugeChange(); + + LLSD handleLanguageIdRequest() const; + LLSD handleSyntaxRequest(const LLSD ¶ms) const; + LLSD handleScriptSubscribe(U32 connection_id, const LLSD& params); + LLSD handleScriptUnsubscribe(U32 connection_id, const LLSD& params); private: - using map_id_to_editor_t = std::unordered_map<std::string, LLHandle<LLPanel>>; + struct EditorSubscription + { + LLHandle<LLPanel> mEditorHandle; + LLScriptEditorWSConnection::wptr_t mConnection; + U32 mConnectionID{ 0 }; + }; + using subscriptions_t = std::unordered_map<std::string, EditorSubscription>; + + SubscriptionError_t updateScriptSubscription(const std::string &script_id, U32 connection_id); + void unsubscribeConnection(U32 connection_id); + + subscriptions_t mSubscriptions; + std::map<U32, LLScriptEditorWSConnection::wptr_t> mActiveConnections; + + boost::signals2::connection mLanguageChangeSignal; + LLUUID mLastSyntaxId; - map_id_to_editor_t mScriptEditors; - std::set<std::shared_ptr<LLScriptEditorWSConnection>> mActiveConnections; - /** - * @brief Connection timeout management - */ LLTimer mCleanupTimer; static constexpr F32 CLEANUP_INTERVAL = 60.0f; // seconds static constexpr F32 CONNECTION_TIMEOUT = 300.0f; // 5 minutes diff --git a/indra/newview/llsyntaxid.cpp b/indra/newview/llsyntaxid.cpp index 1f8766eea2..a891bf945e 100644 --- a/indra/newview/llsyntaxid.cpp +++ b/indra/newview/llsyntaxid.cpp @@ -343,7 +343,7 @@ void LLSyntaxLua::initialize() if (mInitialized) return; loadDefaultKeywordsIntoLLSD(); - + loadLuaTypesIntoLLSD(); mInitialized = true; } @@ -361,3 +361,18 @@ void LLSyntaxLua::loadDefaultKeywordsIntoLLSD() } } } + +void LLSyntaxLua::loadLuaTypesIntoLLSD() +{ + std::string fullFileSpec = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "types_lua_default.llsd"); + llifstream file(fullFileSpec.c_str()); + + if (file.good()) + { + LLSD content; + if (LLSDSerialize::fromXML(content, file) != LLSDParser::PARSE_FAILURE) + { + mTypesXml = content; + } + } +} diff --git a/indra/newview/llsyntaxid.h b/indra/newview/llsyntaxid.h index c24cea1776..51103e3396 100644 --- a/indra/newview/llsyntaxid.h +++ b/indra/newview/llsyntaxid.h @@ -41,11 +41,20 @@ class LLSyntaxIdLSL : public LLSingleton<LLSyntaxIdLSL> LLSINGLETON(LLSyntaxIdLSL); friend class fetchKeywordsFileResponder; +public: + using syntax_id_changed_signal_t = boost::signals2::signal<void()>; + using syntax_id_changed_h = boost::signals2::connection; + + void initialize(); + bool keywordFetchInProgress(); + LLSD getKeywordsXML() const { return mKeywordsXml; }; + LLUUID getSyntaxID() const { return mSyntaxId; } + syntax_id_changed_h addSyntaxIDCallback(const syntax_id_changed_signal_t::slot_type& cb); + private: std::set<std::string> mInflightFetches; - typedef boost::signals2::signal<void()> syntax_id_changed_signal_t; syntax_id_changed_signal_t mSyntaxIDChangedSignal; - boost::signals2::connection mRegionChangedCallback; + syntax_id_changed_h mRegionChangedCallback; bool syntaxIdChanged(); bool isSupportedVersion(const LLSD& content); @@ -67,11 +76,6 @@ private: LLSD mKeywordsXml; bool mInitialized; -public: - void initialize(); - bool keywordFetchInProgress(); - LLSD getKeywordsXML() const { return mKeywordsXml; }; - boost::signals2::connection addSyntaxIDCallback(const syntax_id_changed_signal_t::slot_type& cb); }; @@ -82,11 +86,13 @@ class LLSyntaxLua : public LLSingleton<LLSyntaxLua> public: void initialize(); LLSD getKeywordsXML() const { return mKeywordsXml; } + LLSD getTypesXML() const { return mTypesXml; } private: void loadDefaultKeywordsIntoLLSD(); - + void loadLuaTypesIntoLLSD(); LLSD mKeywordsXml; + LLSD mTypesXml; bool mInitialized; }; |
