summaryrefslogtreecommitdiff
path: root/indra
diff options
context:
space:
mode:
Diffstat (limited to 'indra')
-rw-r--r--indra/llcommon/llstl.h204
-rw-r--r--indra/llcorehttp/llwebsocketmgr.cpp31
-rw-r--r--indra/llcorehttp/llwebsocketmgr.h62
-rw-r--r--indra/newview/CMakeLists.txt2
-rw-r--r--indra/newview/llpreviewscript.cpp48
-rw-r--r--indra/newview/llpreviewscript.h11
-rw-r--r--indra/newview/llscripteditorws.cpp423
-rw-r--r--indra/newview/llscripteditorws.h141
-rw-r--r--indra/newview/llsyntaxid.cpp17
-rw-r--r--indra/newview/llsyntaxid.h22
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 &params) 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;
};