summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--autobuild.xml42
-rw-r--r--indra/cmake/CMakeLists.txt1
-rw-r--r--indra/cmake/Copy3rdPartyLibs.cmake1
-rw-r--r--indra/cmake/Lualibs.cmake15
-rw-r--r--indra/llcommon/CMakeLists.txt1
-rw-r--r--indra/llcommon/llevents.cpp9
-rw-r--r--indra/llcommon/tests/threadsafeschedule_test.cpp4
-rw-r--r--indra/llcommon/tests/workqueue_test.cpp6
-rw-r--r--indra/llui/llmenugl.h16
-rw-r--r--indra/llui/lltexteditor.h2
-rw-r--r--indra/newview/CMakeLists.txt11
-rw-r--r--indra/newview/app_settings/settings.xml11
-rw-r--r--indra/newview/llfilepicker.cpp5
-rw-r--r--indra/newview/llfilepicker.h3
-rw-r--r--indra/newview/llfloaterluadebug.cpp122
-rw-r--r--indra/newview/llfloaterluadebug.h66
-rw-r--r--indra/newview/llinventoryfunctions.cpp13
-rw-r--r--indra/newview/llinventoryfunctions.h16
-rw-r--r--indra/newview/llinventorymodel.cpp11
-rw-r--r--indra/newview/llluamanager.cpp1347
-rw-r--r--indra/newview/llluamanager.h45
-rw-r--r--indra/newview/llstartup.cpp3
-rw-r--r--indra/newview/lltoolplacer.cpp8
-rw-r--r--indra/newview/lltoolplacer.h8
-rw-r--r--indra/newview/lluilistener.cpp2
-rw-r--r--indra/newview/lluilistener.h3
-rw-r--r--indra/newview/llviewerfloaterreg.cpp3
-rw-r--r--indra/newview/llviewermenu.cpp129
-rw-r--r--indra/newview/llviewermenu.h1
-rw-r--r--indra/newview/llviewermenufile.cpp14
-rw-r--r--indra/newview/llviewermenufile.h2
-rw-r--r--indra/newview/skins/default/xui/en/floater_lua_debug.xml108
-rw-r--r--indra/newview/skins/default/xui/en/menu_viewer.xml11
-rw-r--r--indra/newview/tests/llluamanager_test.cpp101
-rwxr-xr-xindra/newview/viewer_manifest.py3
-rw-r--r--indra/test/io.cpp40
-rw-r--r--scripts/lua/avatar.lua14
-rw-r--r--scripts/lua/demo.lua120
38 files changed, 2229 insertions, 88 deletions
diff --git a/autobuild.xml b/autobuild.xml
index 9785884a40..e7c02e930f 100644
--- a/autobuild.xml
+++ b/autobuild.xml
@@ -1810,6 +1810,48 @@
<key>version</key>
<string>1.0.561752</string>
</map>
+ <key>lualibs</key>
+ <map>
+ <key>copyright</key>
+ <string>Lua by Lua.org, PUC-Rio.</string>
+ <key>description</key>
+ <string>LUA libs API</string>
+ <key>license</key>
+ <string>lua</string>
+ <key>license_file</key>
+ <string>LICENSES/lua.txt</string>
+ <key>name</key>
+ <string>lualibs</string>
+ <key>platforms</key>
+ <map>
+ <key>windows</key>
+ <map>
+ <key>archive</key>
+ <map>
+ <key>hash</key>
+ <string>55bd833166d03f1467e2c7f24fa9143e</string>
+ <key>url</key>
+ <string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/87775/805841/openssl-1.1.1l.563846-windows-563846.tar.bz2</string>
+ </map>
+ <key>name</key>
+ <string>windows</string>
+ </map>
+ <key>windows64</key>
+ <map>
+ <key>archive</key>
+ <map>
+ <key>hash</key>
+ <string>6dc51cd8cad422ab1dcd67cc59af119d</string>
+ <key>url</key>
+ <string>https://s3-proxy.lindenlab.com/private-builds-secondlife-com/ct2/118146/1013526/lualibs-5.4.581683-windows64-581683.tar.bz2</string>
+ </map>
+ <key>name</key>
+ <string>windows64</string>
+ </map>
+ </map>
+ <key>version</key>
+ <string>5.4.581683</string>
+ </map>
<key>mesa</key>
<map>
<key>license</key>
diff --git a/indra/cmake/CMakeLists.txt b/indra/cmake/CMakeLists.txt
index f0b35c08f3..af01666875 100644
--- a/indra/cmake/CMakeLists.txt
+++ b/indra/cmake/CMakeLists.txt
@@ -43,6 +43,7 @@ set(cmake_SOURCE_FILES
LLTestCommand.cmake
LLWindow.cmake
Linking.cmake
+ Lualibs.cmake
Meshoptimizer.cmake
NDOF.cmake
OPENAL.cmake
diff --git a/indra/cmake/Copy3rdPartyLibs.cmake b/indra/cmake/Copy3rdPartyLibs.cmake
index d43cc30706..8ae1b5dde5 100644
--- a/indra/cmake/Copy3rdPartyLibs.cmake
+++ b/indra/cmake/Copy3rdPartyLibs.cmake
@@ -61,6 +61,7 @@ if(WINDOWS)
nghttp2.dll
libhunspell.dll
uriparser.dll
+ lua54.dll
)
# OpenSSL
diff --git a/indra/cmake/Lualibs.cmake b/indra/cmake/Lualibs.cmake
new file mode 100644
index 0000000000..ec40d0f41c
--- /dev/null
+++ b/indra/cmake/Lualibs.cmake
@@ -0,0 +1,15 @@
+# -*- cmake -*-
+
+include_guard()
+
+include(Prebuilt)
+
+add_library( ll::lualibs INTERFACE IMPORTED )
+
+use_system_binary( lualibs )
+
+use_prebuilt_binary(lualibs)
+
+target_link_libraries(ll::lualibs INTERFACE ${lualibs})
+
+target_include_directories( ll::lualibs SYSTEM INTERFACE ${LIBS_PREBUILT_DIR}/include/lualibs)
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index ef4899978e..5dd4321330 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -13,6 +13,7 @@ include(Copy3rdPartyLibs)
include(ZLIBNG)
include(URIPARSER)
include(Tracy)
+include(lualibs)
set(llcommon_SOURCE_FILES
diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp
index 0a213bddef..241b8cf463 100644
--- a/indra/llcommon/llevents.cpp
+++ b/indra/llcommon/llevents.cpp
@@ -54,10 +54,11 @@
#pragma warning (pop)
#endif
// other Linden headers
-#include "stringize.h"
#include "llerror.h"
-#include "llsdutil.h"
+#include "lleventfilter.h"
#include "llexception.h"
+#include "llsdutil.h"
+#include "stringize.h"
#if LL_MSVC
#pragma warning (disable : 4702)
#endif
@@ -71,7 +72,9 @@ LLEventPumps::LLEventPumps():
{ "LLEventStream", [](const std::string& name, bool tweak)
{ return new LLEventStream(name, tweak); } },
{ "LLEventMailDrop", [](const std::string& name, bool tweak)
- { return new LLEventMailDrop(name, tweak); } }
+ { return new LLEventMailDrop(name, tweak); } },
+ { "LLEventLogProxy", [](const std::string& name, bool tweak)
+ { return new LLEventLogProxyFor<LLEventStream>(name, tweak); } }
},
mTypes
{
diff --git a/indra/llcommon/tests/threadsafeschedule_test.cpp b/indra/llcommon/tests/threadsafeschedule_test.cpp
index c421cc7b1c..876281eae0 100644
--- a/indra/llcommon/tests/threadsafeschedule_test.cpp
+++ b/indra/llcommon/tests/threadsafeschedule_test.cpp
@@ -46,11 +46,11 @@ namespace tut
// the real time required for each push() call. Explicitly increment
// the timestamp for each one -- but since we're passing explicit
// timestamps, make the queue reorder them.
- queue.push(Queue::TimeTuple(Queue::Clock::now() + 200ms, "ghi"));
+ queue.push(Queue::TimeTuple(Queue::Clock::now() + 2000ms, "ghi"));
// Given the various push() overloads, you have to match the type
// exactly: conversions are ambiguous.
queue.push("abc"s);
- queue.push(Queue::Clock::now() + 100ms, "def");
+ queue.push(Queue::Clock::now() + 1000ms, "def");
queue.close();
auto entry = queue.pop();
ensure_equals("failed to pop first", std::get<0>(entry), "abc"s);
diff --git a/indra/llcommon/tests/workqueue_test.cpp b/indra/llcommon/tests/workqueue_test.cpp
index 1d73f7aa0d..7655a7aa1f 100644
--- a/indra/llcommon/tests/workqueue_test.cpp
+++ b/indra/llcommon/tests/workqueue_test.cpp
@@ -83,7 +83,11 @@ namespace tut
// signal the work item that it can quit; consider LLOneShotCond.
LLCond<Shared> data;
auto start = WorkQueue::TimePoint::clock::now();
- auto interval = 100ms;
+ // 2s seems like a long time to wait, since it directly impacts the
+ // duration of this test program. Unfortunately GitHub's Mac runners
+ // are pretty wimpy, and we're getting spurious "too late" errors just
+ // because the thread doesn't wake up as soon as we want.
+ auto interval = 2s;
queue.postEvery(
interval,
[&data, count = 0]
diff --git a/indra/llui/llmenugl.h b/indra/llui/llmenugl.h
index 87e3f18ebc..d606fef625 100644
--- a/indra/llui/llmenugl.h
+++ b/indra/llui/llmenugl.h
@@ -562,14 +562,15 @@ public:
// add a context menu branch
BOOL appendContextSubMenu(LLMenuGL *menu);
+ // Add the menu item to this menu.
+ virtual BOOL append( LLMenuItemGL* item );
+
+ // add a menu - this will create a cascading menu
+ virtual BOOL appendMenu(LLMenuGL *menu);
+
protected:
void createSpilloverBranch();
void cleanupSpilloverBranch();
- // Add the menu item to this menu.
- virtual BOOL append( LLMenuItemGL* item );
-
- // add a menu - this will create a cascading menu
- virtual BOOL appendMenu( LLMenuGL* menu );
// Used in LLContextMenu and in LLTogleableMenu
// to add an item of context menu branch
@@ -805,9 +806,10 @@ public:
void resetMenuTrigger() { mAltKeyTrigger = FALSE; }
+ // add a menu - this will create a drop down menu.
+ virtual BOOL appendMenu(LLMenuGL *menu);
+
private:
- // add a menu - this will create a drop down menu.
- virtual BOOL appendMenu( LLMenuGL* menu );
// rearrange the child rects so they fit the shape of the menu
// bar.
virtual void arrange( void );
diff --git a/indra/llui/lltexteditor.h b/indra/llui/lltexteditor.h
index f3939248c2..ec2b9a4b7d 100644
--- a/indra/llui/lltexteditor.h
+++ b/indra/llui/lltexteditor.h
@@ -241,7 +241,9 @@ protected:
// Undoable operations
void addChar(llwchar c); // at mCursorPos
S32 addChar(S32 pos, llwchar wc);
+public:
void addLineBreakChar(BOOL group_together = FALSE);
+protected:
S32 overwriteChar(S32 pos, llwchar wc);
void removeChar();
S32 removeChar(S32 pos);
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index c37d03221c..3cfc24b2fb 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -46,6 +46,7 @@ include(VisualLeakDetector)
include(ZLIBNG)
include(URIPARSER)
include(LLPrimitive)
+include(lualibs)
if (NOT HAVOK_TPV)
# When using HAVOK_TPV, the library is precompiled, so no need for this
@@ -237,6 +238,7 @@ set(viewer_SOURCE_FILES
llfloaterlandholdings.cpp
llfloaterlinkreplace.cpp
llfloaterloadprefpreset.cpp
+ llfloaterluadebug.cpp
llfloatermarketplacelistings.cpp
llfloatermap.cpp
llfloatermediasettings.cpp
@@ -365,6 +367,7 @@ set(viewer_SOURCE_FILES
lllogchat.cpp
llloginhandler.cpp
lllogininstance.cpp
+ llluamanager.cpp
llmachineid.cpp
llmanip.cpp
llmaniprotate.cpp
@@ -886,6 +889,7 @@ set(viewer_HEADER_FILES
llfloaterlandholdings.h
llfloaterlinkreplace.h
llfloaterloadprefpreset.h
+ llfloaterluadebug.h
llfloatermap.h
llfloatermarketplacelistings.h
llfloatermediasettings.h
@@ -1012,6 +1016,7 @@ set(viewer_HEADER_FILES
lllogchat.h
llloginhandler.h
lllogininstance.h
+ llluamanager.h
llmachineid.h
llmanip.h
llmaniprotate.h
@@ -1708,6 +1713,7 @@ if (WINDOWS)
${SHARED_LIB_STAGING_DIR}/openjp2.dll
${SHARED_LIB_STAGING_DIR}/libhunspell.dll
${SHARED_LIB_STAGING_DIR}/uriparser.dll
+ ${SHARED_LIB_STAGING_DIR}/lua54.dll
#${SHARED_LIB_STAGING_DIR}/${LL_INTDIR}/SLVoice.exe
#${SHARED_LIB_STAGING_DIR}/${LL_INTDIR}/libsndfile-1.dll
#${SHARED_LIB_STAGING_DIR}/${LL_INTDIR}/vivoxoal.dll
@@ -2295,6 +2301,11 @@ if (LL_TESTS)
"${test_libs}"
)
+ LL_ADD_INTEGRATION_TEST(llluamanager
+ "llluamanager.cpp"
+ "${test_libs}"
+ )
+
LL_ADD_INTEGRATION_TEST(llsechandler_basic
llsechandler_basic.cpp
"${test_libs}"
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index 28d84aba21..bf45d18ea8 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -17074,6 +17074,17 @@
<key>Value</key>
<integer>3</integer>
</map>
+ <key>AutorunLuaScriptName</key>
+ <map>
+ <key>Comment</key>
+ <string>Script name to autorun after login.</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>String</string>
+ <key>Value</key>
+ <string>default.lua</string>
+ </map>
<key>ResetUIScaleOnFirstRun</key>
<map>
<key>Comment</key>
diff --git a/indra/newview/llfilepicker.cpp b/indra/newview/llfilepicker.cpp
index f1f156c2e0..b9435dc9df 100644
--- a/indra/newview/llfilepicker.cpp
+++ b/indra/newview/llfilepicker.cpp
@@ -61,6 +61,7 @@ LLFilePicker LLFilePicker::sInstance;
#define MODEL_FILTER L"Model files (*.dae)\0*.dae\0"
#define SCRIPT_FILTER L"Script files (*.lsl)\0*.lsl\0"
#define DICTIONARY_FILTER L"Dictionary files (*.dic; *.xcu)\0*.dic;*.xcu\0"
+#define LUA_FILTER L"Script files (*.lua)\0*.lua\0"
#endif
#ifdef LL_DARWIN
@@ -218,6 +219,10 @@ BOOL LLFilePicker::setupFilter(ELoadFilter filter)
mOFN.lpstrFilter = DICTIONARY_FILTER \
L"\0";
break;
+ case FFLOAD_LUA:
+ mOFN.lpstrFilter = LUA_FILTER \
+ L"\0";
+ break;
default:
res = FALSE;
break;
diff --git a/indra/newview/llfilepicker.h b/indra/newview/llfilepicker.h
index 692b908fff..cb453ea87e 100644
--- a/indra/newview/llfilepicker.h
+++ b/indra/newview/llfilepicker.h
@@ -85,7 +85,8 @@ public:
FFLOAD_SCRIPT = 11,
FFLOAD_DICTIONARY = 12,
FFLOAD_DIRECTORY = 13, // To call from lldirpicker.
- FFLOAD_EXE = 14 // Note: EXE will be treated as ALL on Windows and Linux but not on Darwin
+ FFLOAD_EXE = 14, // Note: EXE will be treated as ALL on Windows and Linux but not on Darwin
+ FFLOAD_LUA = 15
};
enum ESaveFilter
diff --git a/indra/newview/llfloaterluadebug.cpp b/indra/newview/llfloaterluadebug.cpp
new file mode 100644
index 0000000000..44454d7be0
--- /dev/null
+++ b/indra/newview/llfloaterluadebug.cpp
@@ -0,0 +1,122 @@
+/**
+ * @file llfloaterluadebug.cpp
+ *
+ * $LicenseInfo:firstyear=2023&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2023, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+#include "llfloaterluadebug.h"
+
+#include "lllineeditor.h"
+#include "lltexteditor.h"
+#include "llviewermenufile.h" // LLFilePickerReplyThread
+
+#include "llagent.h"
+#include "llappearancemgr.h"
+#include "llfloaterreg.h"
+#include "llfloaterimnearbychat.h"
+
+#include "llluamanager.h"
+
+#if LL_WINDOWS
+#pragma comment(lib, "liblua54.a")
+#endif
+
+
+LLFloaterLUADebug::LLFloaterLUADebug(const LLSD &key)
+ : LLFloater(key)
+{
+}
+
+
+BOOL LLFloaterLUADebug::postBuild()
+{
+ mResultOutput = getChild<LLTextEditor>("result_text");
+ mLineInput = getChild<LLLineEditor>("lua_cmd");
+ mScriptPath = getChild<LLLineEditor>("script_path");
+ mOutConnection = LLEventPumps::instance().obtain("lua output")
+ .listen("LLFloaterLUADebug",
+ [mResultOutput=mResultOutput](const LLSD& data)
+ {
+ mResultOutput->insertText(data.asString());
+ mResultOutput->addLineBreakChar(true);
+ return false;
+ });
+
+ getChild<LLButton>("execute_btn")->setClickedCallback(boost::bind(&LLFloaterLUADebug::onExecuteClicked, this));
+ getChild<LLButton>("browse_btn")->setClickedCallback(boost::bind(&LLFloaterLUADebug::onBtnBrowse, this));
+ getChild<LLButton>("run_btn")->setClickedCallback(boost::bind(&LLFloaterLUADebug::onBtnRun, this));
+
+#if !LL_WINDOWS
+ getChild<LLButton>("execute_btn")->setEnabled(false);
+ getChild<LLButton>("browse_btn")->setEnabled(false);
+#endif
+
+ return TRUE;
+}
+
+LLFloaterLUADebug::~LLFloaterLUADebug()
+{}
+
+void LLFloaterLUADebug::onExecuteClicked()
+{
+ mResultOutput->setValue("");
+
+ std::string cmd = mLineInput->getText();
+ LLLUAmanager::runScriptLine(cmd, [this](std::string msg)
+ {
+ mResultOutput->insertText(msg);
+ });
+}
+
+void LLFloaterLUADebug::onBtnBrowse()
+{
+ (new LLFilePickerReplyThread(boost::bind(&LLFloaterLUADebug::runSelectedScript, this, _1), LLFilePicker::FFLOAD_LUA, false))->getFile();
+}
+
+void LLFloaterLUADebug::onBtnRun()
+{
+ std::vector<std::string> filenames;
+ std::string filepath = mScriptPath->getText();
+ if (!filepath.empty())
+ {
+ filenames.push_back(filepath);
+ runSelectedScript(filenames);
+ }
+}
+
+void LLFloaterLUADebug::runSelectedScript(const std::vector<std::string> &filenames)
+{
+ mResultOutput->setValue("");
+
+ std::string filepath = filenames[0];
+ if (!filepath.empty())
+ {
+ mScriptPath->setText(filepath);
+ LLLUAmanager::runScriptFile(filepath, [this](std::string msg)
+ {
+ mResultOutput->insertText(msg);
+ });
+ }
+}
+
diff --git a/indra/newview/llfloaterluadebug.h b/indra/newview/llfloaterluadebug.h
new file mode 100644
index 0000000000..fd61e29ebe
--- /dev/null
+++ b/indra/newview/llfloaterluadebug.h
@@ -0,0 +1,66 @@
+/**
+ * @file llfloaterluadebug.h
+ *
+ * $LicenseInfo:firstyear=2023&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2023, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#ifndef LL_LLFLOATERLUADEBUG_H
+#define LL_LLFLOATERLUADEBUG_H
+
+#include "llevents.h"
+#include "llfloater.h"
+
+extern "C"
+{
+#include "lua/lua.h"
+#include "lua/lauxlib.h"
+#include "lua/lualib.h"
+}
+
+class LLLineEditor;
+class LLTextEditor;
+
+class LLFloaterLUADebug :
+ public LLFloater
+{
+ public:
+ LLFloaterLUADebug(const LLSD& key);
+ virtual ~LLFloaterLUADebug();
+
+ /*virtual*/ BOOL postBuild();
+
+ void onExecuteClicked();
+ void onBtnBrowse();
+ void onBtnRun();
+
+ void runSelectedScript(const std::vector<std::string> &filenames);
+
+private:
+ LLTempBoundListener mOutConnection;
+
+ LLTextEditor* mResultOutput;
+ LLLineEditor* mLineInput;
+ LLLineEditor* mScriptPath;
+};
+
+#endif // LL_LLFLOATERLUADEBUG_H
+
diff --git a/indra/newview/llinventoryfunctions.cpp b/indra/newview/llinventoryfunctions.cpp
index 4aeacae6ed..56e680a602 100644
--- a/indra/newview/llinventoryfunctions.cpp
+++ b/indra/newview/llinventoryfunctions.cpp
@@ -2645,6 +2645,19 @@ bool LLNameCategoryCollector::operator()(
return false;
}
+bool LLNameItemCollector::operator()(
+ LLInventoryCategory* cat, LLInventoryItem* item)
+{
+ if(item)
+ {
+ if (!LLStringUtil::compareInsensitive(mName, item->getName()))
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
bool LLFindCOFValidItems::operator()(LLInventoryCategory* cat,
LLInventoryItem* item)
{
diff --git a/indra/newview/llinventoryfunctions.h b/indra/newview/llinventoryfunctions.h
index 925217dda3..d63d4179cf 100644
--- a/indra/newview/llinventoryfunctions.h
+++ b/indra/newview/llinventoryfunctions.h
@@ -367,6 +367,22 @@ protected:
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// Class LLNameItemCollector
+//
+// Collects items based on case-insensitive match of prefix
+//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+class LLNameItemCollector : public LLInventoryCollectFunctor
+{
+public:
+ LLNameItemCollector(const std::string& name) : mName(name) {}
+ virtual ~LLNameItemCollector() {}
+ virtual bool operator()(LLInventoryCategory* cat,
+ LLInventoryItem* item);
+protected:
+ std::string mName;
+};
+
+//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Class LLFindCOFValidItems
//
// Collects items that can be legitimately linked to in the COF.
diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp
index ea771661ec..2849ae8858 100644
--- a/indra/newview/llinventorymodel.cpp
+++ b/indra/newview/llinventorymodel.cpp
@@ -4805,6 +4805,17 @@ std::string LLInventoryModel::getFullPath(const LLInventoryObject *obj) const
return result;
}
+/*
+const LLInventoryObject* LLInventoryModel::findByFullPath(const std::string& path)
+{
+ vector<std::string> path_elts;
+ boost::algorithm::split(path_elts, path, boost::is_any_of("/"));
+ for(path_elts, auto e)
+ {
+ }
+}
+*/
+
///----------------------------------------------------------------------------
/// Local function definitions
///----------------------------------------------------------------------------
diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp
new file mode 100644
index 0000000000..2244fa32f6
--- /dev/null
+++ b/indra/newview/llluamanager.cpp
@@ -0,0 +1,1347 @@
+/**
+ * @file llluamanager.cpp
+ * @brief classes and functions for interfacing with LUA.
+ *
+ * $LicenseInfo:firstyear=2023&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2023, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ *
+ */
+
+#include "llviewerprecompiledheaders.h"
+#include "llluamanager.h"
+
+#include "llerror.h"
+#include "lleventcoro.h"
+#include "lleventfilter.h"
+#include "llevents.h"
+#include "llinstancetracker.h"
+#include "llleaplistener.h"
+#include "lluuid.h"
+#include "stringize.h"
+
+// skip all these link dependencies for integration testing
+#ifndef LL_TEST
+#include "llagent.h"
+#include "llappearancemgr.h"
+#include "llcallbacklist.h"
+#include "llfloaterreg.h"
+#include "llfloaterimnearbychat.h"
+#include "llfloatersidepanelcontainer.h"
+#include "llnotificationsutil.h"
+#include "llvoavatarself.h"
+#include "llviewermenu.h"
+#include "llviewermenufile.h"
+#include "llviewerwindow.h"
+#include "lluilistener.h"
+#include "llanimationstates.h"
+#include "llinventoryfunctions.h"
+#include "lltoolplacer.h"
+#include "llviewerregion.h"
+
+// FIXME extremely hacky way to get to the UI Listener framework. There's
+// a cleaner way.
+extern LLUIListener sUIListener;
+#endif // ! LL_TEST
+
+#include <boost/algorithm/string/replace.hpp>
+
+extern "C"
+{
+#include "lua/lua.h"
+#include "lua/lauxlib.h"
+#include "lua/lualib.h"
+}
+
+#include <algorithm>
+#include <cstdlib> // std::rand()
+#include <cstring> // std::memcpy()
+#include <map>
+#include <memory> // std::unique_ptr
+#include <sstream>
+#include <string_view>
+#include <vector>
+
+#if LL_WINDOWS
+#pragma comment(lib, "liblua54.a")
+#endif
+
+std::string lua_tostdstring(lua_State* L, int index);
+void lua_pushstdstring(lua_State* L, const std::string& str);
+LLSD lua_tollsd(lua_State* L, int index);
+void lua_pushllsd(lua_State* L, const LLSD& data);
+
+/**
+ * LuaListener is based on LLLeap. It serves an analogous function.
+ *
+ * Each LuaListener instance has an int key, generated randomly to
+ * inconvenience malicious Lua scripts wanting to mess with others. The idea
+ * is that a given lua_State stores in its Registry:
+ * - "event.listener": the int key of the corresponding LuaListener, if any
+ * - "event.function": the Lua function to be called with incoming events
+ * The original thought was that LuaListener would itself store the Lua
+ * function -- but surprisingly, there is no C/C++ type in the API that stores
+ * a Lua function.
+ *
+ * (We considered storing in "event.listener" the LuaListener pointer itself
+ * as a light userdata, but the problem would be if Lua code overwrote that.
+ * We want to prevent any Lua script from crashing the viewer, intentionally
+ * or otherwise. Safer to use a key lookup.)
+ *
+ * Like LLLeap, each LuaListener instance also has an associated
+ * LLLeapListener to respond to LLEventPump management commands.
+ */
+class LuaListener: public LLInstanceTracker<LuaListener, int>
+{
+ using super = LLInstanceTracker<LuaListener, int>;
+public:
+ LuaListener(lua_State* L):
+ super(getUniqueKey()),
+ mState(L),
+ mListener(
+ new LLLeapListener(
+ [L](LLEventPump& pump, const std::string& listener)
+ { return connect(L, pump, listener); }))
+ {
+ mReplyConnection = connect(L, mReplyPump, "LuaListener");
+ }
+
+ LuaListener(const LuaListener&) = delete;
+ LuaListener& operator=(const LuaListener&) = delete;
+
+ ~LuaListener()
+ {
+ LL_DEBUGS("Lua") << "~LuaListener('" << mReplyPump.getName() << "')" << LL_ENDL;
+ }
+
+ std::string getReplyName() const { return mReplyPump.getName(); }
+ std::string getCommandName() const { return mListener->getPumpName(); }
+
+private:
+ static int getUniqueKey()
+ {
+ // Find a random key that does NOT already correspond to a LuaListener
+ // instance. Passing a duplicate key to LLInstanceTracker would do Bad
+ // Things.
+ int key;
+ do
+ {
+ key = std::rand();
+ } while (LuaListener::getInstance(key));
+ // This is theoretically racy, if we were instantiating new
+ // LuaListeners on multiple threads. Don't.
+ return key;
+ }
+
+ static LLBoundListener connect(lua_State* L, LLEventPump& pump, const std::string& listener)
+ {
+ return pump.listen(
+ listener,
+ [L, pumpname=pump.getName()](const LLSD& data)
+ { return call_lua(L, pumpname, data); });
+ }
+
+ static bool call_lua(lua_State* L, const std::string& pump, const LLSD& data)
+ {
+ LL_INFOS("Lua") << "LuaListener::call_lua('" << pump << "', " << data << ")" << LL_ENDL;
+ if (! lua_checkstack(L, 3))
+ {
+ LL_WARNS("Lua") << "Cannot extend Lua stack to call listen_events() callback"
+ << LL_ENDL;
+ return false;
+ }
+ // push the registered Lua callback function stored in our registry as
+ // "event.function"
+ lua_getfield(L, LUA_REGISTRYINDEX, "event.function");
+ llassert(lua_isfunction(L, -1));
+ // pass pump name
+ lua_pushstdstring(L, pump);
+ // then the data blob
+ lua_pushllsd(L, data);
+ // call the registered Lua listener function; allow it to return bool;
+ // no message handler
+ auto status = lua_pcall(L, 2, 1, 0);
+ bool result{ false };
+ if (status != LUA_OK)
+ {
+ LL_WARNS("Lua") << "Error in listen_events() callback: "
+ << lua_tostdstring(L, -1) << LL_ENDL;
+ }
+ else
+ {
+ result = lua_toboolean(L, -1);
+ }
+ // discard either the error message or the bool return value
+ lua_pop(L, 1);
+ return result;
+ }
+
+ lua_State* mState;
+#ifndef LL_TEST
+ LLEventStream mReplyPump{ LLUUID::generateNewID().asString() };
+#else
+ LLEventLogProxyFor<LLEventStream> mReplyPump{ "luapump", false };
+#endif
+ LLTempBoundListener mReplyConnection;
+ std::unique_ptr<LLLeapListener> mListener;
+};
+
+/**
+ * LuaPopper is an RAII struct whose role is to pop some number of entries
+ * from the Lua stack if the calling function exits early.
+ */
+struct LuaPopper
+{
+ LuaPopper(lua_State* L, int count):
+ mState(L),
+ mCount(count)
+ {}
+
+ LuaPopper(const LuaPopper&) = delete;
+ LuaPopper& operator=(const LuaPopper&) = delete;
+
+ ~LuaPopper()
+ {
+ if (mCount)
+ {
+ lua_pop(mState, mCount);
+ }
+ }
+
+ void disarm() { set(0); }
+ void set(int count) { mCount = count; }
+
+ lua_State* mState;
+ int mCount;
+};
+
+/**
+ * LuaFunction is a base class containing a static registry of its static
+ * subclass call() methods. call() is NOT virtual: instead, each subclass
+ * constructor passes a pointer to its distinct call() method to the base-
+ * class constructor, along with a name by which to register that method.
+ *
+ * The init() method walks the registry and registers each such name with the
+ * passed lua_State.
+ */
+class LuaFunction
+{
+public:
+ LuaFunction(const std::string_view& name, lua_CFunction function)
+ {
+ getRegistry().emplace(name, function);
+ }
+
+ static void init(lua_State* L)
+ {
+ for (const auto& pair: getRegistry())
+ {
+ lua_register(L, pair.first.c_str(), pair.second);
+ }
+ }
+
+ static lua_CFunction get(const std::string& key)
+ {
+ // use find() instead of subscripting to avoid creating an entry for
+ // unknown key
+ const auto& registry{ getRegistry() };
+ auto found{ registry.find(key) };
+ return (found == registry.end())? nullptr : found->second;
+ }
+
+private:
+ using Registry = std::map<std::string, lua_CFunction>;
+ static Registry& getRegistry()
+ {
+ // use a function-local static to ensure it's initialized
+ static Registry registry;
+ return registry;
+ }
+};
+
+/**
+ * lua_function(name) is a macro to facilitate defining C++ functions
+ * available to Lua. It defines a subclass of LuaFunction and declares a
+ * static instance of that subclass, thereby forcing the compiler to call its
+ * constructor at module initialization time. The constructor passes the
+ * stringized instance name to its LuaFunction base-class constructor, along
+ * with a pointer to the static subclass call() method. It then emits the
+ * call() method definition header, to be followed by a method body enclosed
+ * in curly braces as usual.
+ */
+#define lua_function(name) \
+static struct name##_ : public LuaFunction \
+{ \
+ name##_(): LuaFunction(#name, &call) {} \
+ static int call(lua_State* L); \
+} name; \
+int name##_::call(lua_State* L)
+// {
+// ... supply method body here, referencing 'L' ...
+// }
+
+// This function consumes ALL Lua stack arguments and returns concatenated
+// message string
+std::string lua_print_msg(lua_State* L, const std::string_view& level)
+{
+ // On top of existing Lua arguments, push 'where' info
+ luaL_checkstack(L, 1, nullptr);
+ luaL_where(L, 1);
+ // start with the 'where' info at the top of the stack
+ std::ostringstream out;
+ out << lua_tostring(L, -1);
+ lua_pop(L, 1);
+ const char* sep = ""; // 'where' info ends with ": "
+ // now iterate over arbitrary args, calling Lua tostring() on each and
+ // concatenating with separators
+ for (int p = 1; p <= lua_gettop(L); ++p)
+ {
+ out << sep;
+ sep = " ";
+ // push Lua tostring() function -- note, semantically different from
+ // lua_tostring()!
+ lua_getglobal(L, "tostring");
+ // Now the stack is arguments 1 .. N, plus tostring().
+ // Rotate downwards, producing stack args 2 .. N, tostring(), arg1.
+ lua_rotate(L, 1, -1);
+ // pop tostring() and arg1, pushing tostring(arg1)
+ // (ignore potential error code from lua_pcall() because, if there was
+ // an error, we expect the stack top to be an error message -- which
+ // we'll print)
+ lua_pcall(L, 1, 1, 0);
+ // stack now holds args 2 .. N, tostring(arg1)
+ out << lua_tostring(L, -1);
+ }
+ // pop everything
+ lua_settop(L, 0);
+ // capture message string
+ std::string msg{ out.str() };
+ // put message out there for any interested party (*koff* LLFloaterLUADebug *koff*)
+ LLEventPumps::instance().obtain("lua output").post(stringize(level, ": ", msg));
+ return msg;
+}
+
+lua_function(print_debug)
+{
+ LL_DEBUGS("Lua") << lua_print_msg(L, "DEBUG") << LL_ENDL;
+ return 0;
+}
+
+// also used for print(); see LuaState constructor
+lua_function(print_info)
+{
+ LL_INFOS("Lua") << lua_print_msg(L, "INFO") << LL_ENDL;
+ return 0;
+}
+
+lua_function(print_warning)
+{
+ LL_WARNS("Lua") << lua_print_msg(L, "WARN") << LL_ENDL;
+ return 0;
+}
+
+#ifndef LL_TEST
+lua_function(avatar_sit)
+{
+ gAgent.sitDown();
+ return 0;
+}
+
+lua_function(avatar_stand)
+{
+ gAgent.standUp();
+ return 0;
+}
+
+lua_function(nearby_chat_send)
+{
+ std::string msg(lua_tostring(L, 1));
+ LLFloaterIMNearbyChat *nearby_chat = LLFloaterReg::findTypedInstance<LLFloaterIMNearbyChat>("nearby_chat");
+ nearby_chat->sendChatFromViewer(msg, CHAT_TYPE_NORMAL, gSavedSettings.getBOOL("PlayChatAnim"));
+
+ lua_pop(L, 1);
+ return 0;
+}
+
+lua_function(wear_by_name)
+{
+ std::string folder_name(lua_tostring(L, 1));
+ LLAppearanceMgr::instance().wearOutfitByName(folder_name);
+
+ lua_pop(L, 1);
+ return 0;
+}
+
+lua_function(open_floater)
+{
+ std::string floater_name(lua_tostring(L, 1));
+
+ LLSD key;
+ if (floater_name == "profile")
+ {
+ key["id"] = gAgentID;
+ }
+ LLFloaterReg::showInstance(floater_name, key);
+
+ lua_pop(L, 1);
+ return 0;
+}
+
+lua_function(close_floater)
+{
+ std::string floater_name(lua_tostring(L, 1));
+
+ LLSD key;
+ if (floater_name == "profile")
+ {
+ key["id"] = gAgentID;
+ }
+ LLFloaterReg::hideInstance(floater_name, key);
+
+ lua_pop(L, 1);
+ return 0;
+}
+
+lua_function(close_all_floaters)
+{
+ close_all_windows();
+ return 0;
+}
+
+lua_function(click_child)
+{
+ std::string parent_name(lua_tostring(L, 1));
+ std::string child_name(lua_tostring(L, 2));
+
+ LLFloater *floater = LLFloaterReg::findInstance(parent_name);
+ LLUICtrl *child = floater->getChild<LLUICtrl>(child_name, true);
+ child->onCommit();
+
+ lua_pop(L, 2);
+ return 0;
+}
+
+lua_function(snapshot_to_file)
+{
+ std::string filename(lua_tostring(L, 1));
+
+ //don't take snapshot from coroutine
+ doOnIdleOneTime([filename]()
+ {
+ gViewerWindow->saveSnapshot(filename,
+ gViewerWindow->getWindowWidthRaw(),
+ gViewerWindow->getWindowHeightRaw(),
+ gSavedSettings.getBOOL("RenderUIInSnapshot"),
+ gSavedSettings.getBOOL("RenderHUDInSnapshot"),
+ FALSE,
+ LLSnapshotModel::SNAPSHOT_TYPE_COLOR,
+ LLSnapshotModel::SNAPSHOT_FORMAT_PNG);
+ });
+
+ lua_pop(L, 1);
+ return 0;
+}
+
+lua_function(open_wearing_tab)
+{
+ LLFloaterSidePanelContainer::showPanel("appearance", LLSD().with("type", "now_wearing"));
+ return 0;
+}
+
+lua_function(set_debug_setting_bool)
+{
+ std::string setting_name(lua_tostring(L, 1));
+ bool value(lua_toboolean(L, 2));
+
+ gSavedSettings.setBOOL(setting_name, value);
+ lua_pop(L, 2);
+ return 0;
+}
+
+lua_function(get_avatar_name)
+{
+ std::string name = gAgentAvatarp->getFullname();
+ luaL_checkstack(L, 1, nullptr);
+ lua_pushstdstring(L, name);
+ return 1;
+}
+
+lua_function(is_avatar_flying)
+{
+ luaL_checkstack(L, 1, nullptr);
+ lua_pushboolean(L, gAgent.getFlying());
+ return 1;
+}
+
+lua_function(play_animation)
+{
+ // on exit, pop all passed arguments, so always return 0
+ LuaPopper popper(L, lua_gettop(L));
+
+ std::string anim_name = lua_tostring(L,1);
+
+ EAnimRequest req = ANIM_REQUEST_START;
+ if (lua_gettop(L) > 1)
+ {
+ req = (EAnimRequest) (int) lua_tonumber(L, 2);
+ }
+
+ LLInventoryModel::cat_array_t cat_array;
+ LLInventoryModel::item_array_t item_array;
+ LLNameItemCollector has_name(anim_name);
+ gInventory.collectDescendentsIf(gInventory.getRootFolderID(),
+ cat_array,
+ item_array,
+ LLInventoryModel::EXCLUDE_TRASH,
+ has_name);
+ for (auto& item: item_array)
+ {
+ if (item->getType() == LLAssetType::AT_ANIMATION)
+ {
+ LLUUID anim_id = item->getAssetUUID();
+ LL_INFOS() << "Playing animation " << anim_id << LL_ENDL;
+ gAgent.sendAnimationRequest(anim_id, req);
+ return 0;
+ }
+ }
+ LL_WARNS() << "No animation found for name " << anim_name << LL_ENDL;
+
+ return 0;
+}
+
+lua_function(env_setting_event)
+{
+ handle_env_setting_event(lua_tostring(L, 1));
+ lua_pop(L, 1);
+ return 0;
+}
+
+void handle_notification_dialog(const LLSD &notification, const LLSD &response, lua_State *L, std::string response_cb)
+{
+ if (!response_cb.empty())
+ {
+ S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
+
+ luaL_checkstack(L, 1, nullptr);
+ lua_pushinteger(L, option);
+ lua_setglobal(L, response_cb.c_str());
+ }
+}
+
+lua_function(show_notification)
+{
+ std::string notification(lua_tostring(L, 1));
+
+ if (lua_type(L, 2) == LUA_TTABLE)
+ {
+ LLSD args = lua_tollsd(L, 2);
+
+ std::string response_cb;
+ if (lua_type(L, 3) == LUA_TSTRING)
+ {
+ response_cb = lua_tostring(L, 3);
+ }
+
+ LLNotificationsUtil::add(notification, args, LLSD(), boost::bind(handle_notification_dialog, _1, _2, L, response_cb));
+ }
+ else if (lua_type(L, 2) == LUA_TSTRING)
+ {
+ std::string response_cb = lua_tostring(L, 2);
+ LLNotificationsUtil::add(notification, LLSD(), LLSD(), boost::bind(handle_notification_dialog, _1, _2, L, response_cb));
+ }
+ else
+ {
+ LLNotificationsUtil::add(notification);
+ }
+
+ lua_settop(L, 0);
+ return 0;
+}
+
+lua_function(add_menu_item)
+{
+ std::string menu(lua_tostring(L, 1));
+ if (lua_type(L, 2) == LUA_TTABLE)
+ {
+ LLSD args = lua_tollsd(L, 2);
+
+ LLMenuItemCallGL::Params item_params;
+ item_params.name = args["name"];
+ item_params.label = args["label"];
+
+ LLUICtrl::CommitCallbackParam item_func;
+ item_func.function_name = args["function"];
+ if (args.has("parameter"))
+ {
+ item_func.parameter = args["parameter"];
+ }
+ item_params.on_click = item_func;
+
+ LLMenuItemCallGL *menu_item = LLUICtrlFactory::create<LLMenuItemCallGL>(item_params);
+ gMenuBarView->findChildMenuByName(menu, true)->append(menu_item);
+ }
+
+ lua_settop(L, 0);
+ return 0;
+}
+
+lua_function(add_menu_separator)
+{
+ std::string menu(lua_tostring(L, 1));
+ gMenuBarView->findChildMenuByName(menu, true)->addSeparator();
+
+ lua_pop(L, 1);
+ return 0;
+}
+
+lua_function(add_menu)
+{
+ if (lua_type(L, 1) == LUA_TTABLE)
+ {
+ LLSD args = lua_tollsd(L, 1);
+
+ LLMenuGL::Params item_params;
+ item_params.name = args["name"];
+ item_params.label = args["label"];
+ item_params.can_tear_off = args["tear_off"];
+
+ LLMenuGL *menu = LLUICtrlFactory::create<LLMenuGL>(item_params);
+ gMenuBarView->appendMenu(menu);
+ }
+
+ lua_settop(L, 0);
+ return 0;
+}
+
+lua_function(add_branch)
+{
+ std::string menu(lua_tostring(L, 1));
+ if (lua_type(L, 2) == LUA_TTABLE)
+ {
+ LLSD args = lua_tollsd(L, 2);
+
+ LLMenuGL::Params item_params;
+ item_params.name = args["name"];
+ item_params.label = args["label"];
+ item_params.can_tear_off = args["tear_off"];
+
+ LLMenuGL *branch = LLUICtrlFactory::create<LLMenuGL>(item_params);
+ gMenuBarView->findChildMenuByName(menu, true)->appendMenu(branch);
+ }
+
+ lua_settop(L, 0);
+ return 0;
+}
+
+// rez_prim({x, y}, prim_type)
+// avatar is the reference point
+lua_function(rez_prim)
+{
+ lua_rawgeti(L, 1, 1);
+ F32 x = lua_tonumber(L, -1);
+ lua_pop(L, 1);
+ lua_rawgeti(L, 1, 2);
+ F32 y = lua_tonumber(L, -1);
+ lua_pop(L, 1);
+
+ S32 type(lua_tonumber(L, 2)); // primitive shapes 1-8
+
+ LLVector3 obj_pos = gAgent.getPositionAgent() + LLVector3(x, y, -0.5);
+ bool res = LLToolPlacer::rezNewObject(type, NULL, 0, TRUE, gAgent.getPositionAgent(), obj_pos, gAgent.getRegion(), 0);
+
+ LL_INFOS() << "Rezing a prim: type " << LLPrimitive::pCodeToString(type) << ", coordinates: " << obj_pos << " Success: " << res << LL_ENDL;
+
+ lua_settop(L, 0);
+ return 0;
+}
+
+
+void move_to_dest(const LLVector3d &target_global, lua_State *L, std::string response_cb)
+{
+ struct Data
+ {
+ lua_State *L;
+ std::string response_cb;
+ };
+
+ Data *data = new Data();
+ data->L = L;
+ if (!response_cb.empty())
+ {
+ data->response_cb = response_cb;
+ }
+
+ auto handle_dest_reached = [](BOOL success, void *user_data)
+ {
+ Data *cb_data = static_cast<Data *>(user_data);
+ if (!cb_data->response_cb.empty())
+ {
+ S32 result = success ? 1 : -1;
+ lua_pushinteger(cb_data->L, result);
+ lua_setglobal(cb_data->L, cb_data->response_cb.c_str());
+ }
+ };
+
+ gAgent.startAutoPilotGlobal(target_global, std::string(), NULL, handle_dest_reached, data, 0.f, 0.03f, FALSE);
+}
+
+// move_by({x,y}, "lua_cb_func")
+// avatar is the reference point
+lua_function(move_by)
+{
+ lua_rawgeti(L, 1, 1);
+ F32 x = lua_tonumber(L, -1);
+ lua_pop(L, 1);
+ lua_rawgeti(L, 1, 2);
+ F32 y = lua_tonumber(L, -1);
+ lua_pop(L, 1);
+
+ LLVector3d dest = gAgent.getRegion()->getPosGlobalFromRegion(gAgent.getPositionAgent() + LLVector3(x, y, 0));
+
+ std::string response_cb;
+ if (lua_type(L, 2) == LUA_TSTRING)
+ {
+ response_cb = lua_tostring(L, 2);
+ }
+ move_to_dest(dest, L, response_cb);
+
+ lua_settop(L, 0);
+ return 0;
+}
+
+// move_to({x,y,z}, "lua_cb_func")
+// region coordinates are used
+lua_function(move_to)
+{
+ lua_rawgeti(L, 1, 1);
+ F32 x = lua_tonumber(L, -1);
+ lua_pop(L, 1);
+ lua_rawgeti(L, 1, 2);
+ F32 y = lua_tonumber(L, -1);
+ lua_rawgeti(L, 1, 3);
+ F32 z = lua_tonumber(L, -1);
+ lua_pop(L, 1);
+
+ LLVector3d dest = gAgent.getRegion()->getPosGlobalFromRegion(LLVector3(x, y, z));
+
+ std::string response_cb;
+ if (lua_type(L, 2) == LUA_TSTRING)
+ {
+ response_cb = lua_tostring(L, 2);
+ }
+ move_to_dest(dest, L, response_cb);
+
+ lua_settop(L, 0);
+ return 0;
+}
+
+lua_function(run_ui_command)
+{
+ int top = lua_gettop(L);
+ std::string func_name;
+ if (top >= 1)
+ {
+ func_name = lua_tostring(L,1);
+ }
+ std::string parameter;
+ if (top >= 2)
+ {
+ parameter = lua_tostring(L,2);
+ }
+ LL_WARNS("LUA") << "running ui func " << func_name << " parameter " << parameter << LL_ENDL;
+ LLSD event;
+ event["function"] = func_name;
+ if (!parameter.empty())
+ {
+ event["parameter"] = parameter;
+ }
+ sUIListener.call(event);
+
+ lua_settop(L, 0);
+ return 0;
+}
+#endif // ! LL_TEST
+
+lua_function(post_on)
+{
+ std::string pumpname{ lua_tostdstring(L, 1) };
+ LLSD data{ lua_tollsd(L, 2) };
+ lua_pop(L, 2);
+ LL_INFOS("Lua") << "post_on('" << pumpname << "', " << data << ")" << LL_ENDL;
+ LLEventPumps::instance().obtain(pumpname).post(data);
+ return 0;
+}
+
+lua_function(listen_events)
+{
+ if (! lua_isfunction(L, 1))
+ {
+ return luaL_typeerror(L, 1, "function");
+ }
+ luaL_checkstack(L, 2, nullptr);
+
+ // Get the lua_State* for the main thread of this state, in case we were
+ // called from a coroutine thread. We're going to make callbacks into Lua
+ // code, and we want to do it on the main thread rather than a (possibly
+ // suspended) coroutine thread.
+ // Registry table is at pseudo-index LUA_REGISTRYINDEX
+ // Main thread is at registry key LUA_RIDX_MAINTHREAD
+ auto regtype{ lua_geti(L, LUA_REGISTRYINDEX, LUA_RIDX_MAINTHREAD) };
+ // Not finding the main thread at the documented place isn't a user error,
+ // it's a Problem
+ llassert_always(regtype == LUA_TTHREAD);
+ lua_State* mainthread{ lua_tothread(L, -1) };
+ // pop the main thread
+ lua_pop(L, 1);
+
+ luaL_checkstack(mainthread, 1, nullptr);
+ LuaListener::ptr_t listener;
+ // Does the main thread already have a LuaListener stored in the registry?
+ // That is, has this Lua chunk already called listen_events()?
+ auto keytype{ lua_getfield(mainthread, LUA_REGISTRYINDEX, "event.listener") };
+ llassert(keytype == LUA_TNIL || keytype == LUA_TNUMBER);
+ if (keytype == LUA_TNUMBER)
+ {
+ // We do already have a LuaListener. Retrieve it.
+ int isint;
+ listener = LuaListener::getInstance(lua_tointegerx(mainthread, -1, &isint));
+ // pop the int "event.listener" key
+ lua_pop(mainthread, 1);
+ // Nobody should have destroyed this LuaListener instance!
+ llassert(isint && listener);
+ }
+ else
+ {
+ // pop the nil "event.listener" key
+ lua_pop(mainthread, 1);
+ // instantiate a new LuaListener, binding the mainthread state -- but
+ // use a no-op deleter: we do NOT want to delete this new LuaListener
+ // on return from listen_events()!
+ listener.reset(new LuaListener(mainthread), [](LuaListener*){});
+ // set its key in the field where we'll look for it later
+ lua_pushinteger(mainthread, listener->getKey());
+ lua_setfield(mainthread, LUA_REGISTRYINDEX, "event.listener");
+ }
+
+ // Now that we've found or created our LuaListener, store the passed Lua
+ // function as the callback. Beware: our caller passed the function on L's
+ // stack, but we want to store it on the mainthread registry.
+ if (L != mainthread)
+ {
+ // push 1 value (the Lua function) from L's stack to mainthread's
+ lua_xmove(L, mainthread, 1);
+ }
+ lua_setfield(mainthread, LUA_REGISTRYINDEX, "event.function");
+
+ // return the reply pump name and the command pump name on caller's lua_State
+ lua_pushstdstring(L, listener->getReplyName());
+ lua_pushstdstring(L, listener->getCommandName());
+ return 2;
+}
+
+lua_function(await_event)
+{
+ // await_event(pumpname [, timeout [, value to return if timeout (default nil)]])
+ auto pumpname{ lua_tostdstring(L, 1) };
+ LLSD result;
+ if (lua_gettop(L) > 1)
+ {
+ auto timeout{ lua_tonumber(L, 2) };
+ // with no 3rd argument, should be LLSD()
+ auto dftval{ lua_tollsd(L, 3) };
+ lua_settop(L, 0);
+ result = llcoro::suspendUntilEventOnWithTimeout(pumpname, timeout, dftval);
+ }
+ else
+ {
+ // no timeout
+ lua_pop(L, 1);
+ result = llcoro::suspendUntilEventOn(pumpname);
+ }
+ lua_pushllsd(L, result);
+ return 1;
+}
+
+/**
+ * RAII class to manage the lifespan of a lua_State
+ */
+class LuaState
+{
+public:
+ LuaState(const std::string_view& desc, LLLUAmanager::script_finished_fn cb):
+ mDesc(desc),
+ mCallback(cb),
+ mState(luaL_newstate())
+ {
+ luaL_openlibs(mState);
+ LuaFunction::init(mState);
+ // Try to make print() write to our log.
+ lua_register(mState, "print", LuaFunction::get("print_info"));
+ }
+
+ LuaState(const LuaState&) = delete;
+ LuaState& operator=(const LuaState&) = delete;
+
+ ~LuaState()
+ {
+ // Did somebody call listen_events() on this LuaState?
+ // That is, is there a LuaListener key in its registry?
+ auto keytype{ lua_getfield(mState, LUA_REGISTRYINDEX, "event.listener") };
+ if (keytype == LUA_TNUMBER)
+ {
+ // We do have a LuaListener. Retrieve it.
+ int isint;
+ auto listener{ LuaListener::getInstance(lua_tointegerx(mState, -1, &isint)) };
+ // pop the int "event.listener" key
+ lua_pop(mState, 1);
+ // if we got a LuaListener instance, destroy it
+ // (if (! isint), lua_tointegerx() returned 0, but key 0 might
+ // validly designate someone ELSE's LuaListener)
+ if (isint && listener)
+ {
+ auto lptr{ listener.get() };
+ listener.reset();
+ delete lptr;
+ }
+ }
+
+ lua_close(mState);
+
+ if (mCallback)
+ {
+ // mError potentially set by previous checkLua() call(s)
+ mCallback(mError);
+ }
+ }
+
+ bool checkLua(int r)
+ {
+ if (r != LUA_OK)
+ {
+ mError = lua_tostring(mState, -1);
+ lua_pop(mState, 1);
+
+ LL_WARNS() << mDesc << ": " << mError << LL_ENDL;
+ return false;
+ }
+ return true;
+ }
+
+ operator lua_State*() const { return mState; }
+
+private:
+ std::string mDesc;
+ LLLUAmanager::script_finished_fn mCallback;
+ lua_State* mState;
+ std::string mError;
+};
+
+void LLLUAmanager::runScriptFile(const std::string& filename, script_finished_fn cb)
+{
+ std::string desc{ stringize("runScriptFile('", filename, "')") };
+ LLCoros::instance().launch(desc, [desc, filename, cb]()
+ {
+ LuaState L(desc, cb);
+
+ auto LUA_sleep_func = [](lua_State *L)
+ {
+ F32 seconds = lua_tonumber(L, -1);
+ lua_pop(L, 1);
+ llcoro::suspendUntilTimeout(seconds);
+ return 0;
+ };
+
+ lua_register(L, "sleep", LUA_sleep_func);
+
+ if (L.checkLua(luaL_dofile(L, filename.c_str())))
+ {
+ lua_getglobal(L, "call_once_func");
+ if (lua_isfunction(L, -1))
+ {
+ // call call_once_func(), setting internal error message if
+ // error
+ L.checkLua(lua_pcall(L, 0, 0, 0));
+ }
+ }
+ });
+}
+
+void LLLUAmanager::runScriptLine(const std::string& cmd, script_finished_fn cb)
+{
+ // find a suitable abbreviation for the cmd string
+ std::string_view shortcmd{ cmd };
+ const size_t shortlen = 40;
+ std::string::size_type eol = shortcmd.find_first_of("\r\n");
+ if (eol != std::string::npos)
+ shortcmd = shortcmd.substr(0, eol);
+ if (shortcmd.length() > shortlen)
+ shortcmd = stringize(shortcmd.substr(0, shortlen), "...");
+
+ std::string desc{ stringize("runScriptLine('", shortcmd, "')") };
+ LLCoros::instance().launch(desc, [desc, cmd, cb]()
+ {
+ LuaState L(desc, cb);
+ L.checkLua(luaL_dostring(L, cmd.c_str()));
+ });
+}
+
+void LLLUAmanager::runScriptOnLogin()
+{
+#ifndef LL_TEST
+ std::string filename = gSavedSettings.getString("AutorunLuaScriptName");
+ if (filename.empty())
+ {
+ LL_INFOS() << "Script name wasn't set." << LL_ENDL;
+ return;
+ }
+
+ filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, filename);
+ if (!gDirUtilp->fileExists(filename))
+ {
+ LL_INFOS() << filename << " was not found." << LL_ENDL;
+ return;
+ }
+
+ runScriptFile(filename);
+#endif // ! LL_TEST
+}
+
+std::string lua_tostdstring(lua_State* L, int index)
+{
+ size_t len;
+ const char* strval{ lua_tolstring(L, index, &len) };
+ return { strval, len };
+}
+
+void lua_pushstdstring(lua_State* L, const std::string& str)
+{
+ luaL_checkstack(L, 1, nullptr);
+ lua_pushlstring(L, str.c_str(), str.length());
+}
+
+// By analogy with existing lua_tomumble() functions, return an LLSD object
+// corresponding to the Lua object at stack index 'index' in state L.
+// This function assumes that a Lua caller is fully aware that they're trying
+// to call a viewer function. In other words, the caller must specifically
+// construct Lua data convertible to LLSD.
+//
+// For proper error handling, we REQUIRE that the Lua runtime be compiled as
+// C++ so errors are raised as C++ exceptions rather than as longjmp() calls:
+// http://www.lua.org/manual/5.4/manual.html#4.4
+// "Internally, Lua uses the C longjmp facility to handle errors. (Lua will
+// use exceptions if you compile it as C++; search for LUAI_THROW in the
+// source code for details.)"
+// Some blocks within this function construct temporary C++ objects in the
+// expectation that these objects will be properly destroyed even if code
+// reached by that block raises a Lua error.
+LLSD lua_tollsd(lua_State* L, int index)
+{
+ switch (lua_type(L, index))
+ {
+ case LUA_TNONE:
+ // Should LUA_TNONE be an error instead of returning isUndefined()?
+ case LUA_TNIL:
+ return {};
+
+ case LUA_TBOOLEAN:
+ return bool(lua_toboolean(L, index));
+
+ case LUA_TNUMBER:
+ {
+ // check if integer truncation leaves the number intact
+ int isint;
+ lua_Integer intval{ lua_tointegerx(L, index, &isint) };
+ if (isint)
+ {
+ return LLSD::Integer(intval);
+ }
+ else
+ {
+ return lua_tonumber(L, index);
+ }
+ }
+
+ case LUA_TSTRING:
+ return lua_tostdstring(L, index);
+
+ case LUA_TUSERDATA:
+ {
+ LLSD::Binary binary(lua_rawlen(L, index));
+ std::memcpy(binary.data(), lua_touserdata(L, index), binary.size());
+ return binary;
+ }
+
+ case LUA_TTABLE:
+ {
+ // A Lua table correctly constructed to convert to LLSD will have
+ // either consecutive integer keys starting at 1, which we represent
+ // as an LLSD array (with Lua key 1 at C++ index 0), or will have
+ // all string keys.
+ //
+ // In the belief that Lua table traversal skips "holes," that is, it
+ // doesn't report any key/value pair whose value is nil, we allow a
+ // table with integer keys >= 1 but with "holes." This produces an
+ // LLSD array with isUndefined() entries at unspecified keys. There
+ // would be no other way for a Lua caller to construct an
+ // isUndefined() LLSD array entry. However, to guard against crazy int
+ // keys, we forbid gaps larger than a certain size: crazy int keys
+ // could result in a crazy large contiguous LLSD array.
+ //
+ // Possible looseness could include:
+ // - A mix of integer and string keys could produce an LLSD map in
+ // which the integer keys are converted to string. (Key conversion
+ // must be performed in C++, not Lua, to avoid confusing
+ // lua_next().)
+ // - However, since in Lua t[0] and t["0"] are distinct table entries,
+ // do not consider converting numeric string keys to int to return
+ // an LLSD array.
+ // But until we get more experience with actual Lua scripts in
+ // practice, let's say that any deviation is a Lua coding error.
+ // An important property of the strict definition above is that most
+ // conforming data blobs can make a round trip across the language
+ // boundary and still compare equal. A non-conforming data blob would
+ // lose that property.
+ // Known exceptions to round trip identity:
+ // - Empty LLSD map and empty LLSD array convert to empty Lua table.
+ // But empty Lua table converts to isUndefined() LLSD object.
+ // - LLSD::Real with integer value returns as LLSD::Integer.
+ // - LLSD::UUID, LLSD::Date and LLSD::URI all convert to Lua string,
+ // and so return as LLSD::String.
+
+ // This is the most important of the luaL_checkstack() calls because a
+ // deeply nested Lua structure will enter this case at each level, and
+ // we'll need another 2 stack slots to traverse each nested table.
+ luaL_checkstack(L, 2, nullptr);
+ lua_pushnil(L); // first key
+ if (! lua_next(L, index))
+ {
+ // it's a table, but the table is empty -- no idea if it should be
+ // modeled as empty array or empty map -- return isUndefined(),
+ // which can be consumed as either
+ return {};
+ }
+ // key is at stack index -2, value at index -1
+ // from here until lua_next() returns 0, have to lua_pop(2) if we
+ // return early
+ LuaPopper popper(L, 2);
+ // Remember the type of the first key
+ auto firstkeytype{ lua_type(L, -2) };
+ switch (firstkeytype)
+ {
+ case LUA_TNUMBER:
+ {
+ // First Lua key is a number: try to convert table to LLSD array.
+ // This is tricky because we don't know in advance the size of the
+ // array. The Lua reference manual says that lua_rawlen() is the
+ // same as the length operator '#'; but the length operator states
+ // that it might stop at any "hole" in the subject table.
+ // Moreover, the Lua next() function (and presumably lua_next())
+ // traverses a table in unspecified order, even for numeric keys
+ // (emphasized in the doc).
+ // Make a preliminary pass over the whole table to validate and to
+ // collect keys.
+ std::vector<LLSD::Integer> keys;
+ // Try to determine the length of the table. If the length
+ // operator is truthful, avoid allocations while we grow the keys
+ // vector. Even if it's not, we can still grow the vector, albeit
+ // a little less efficiently.
+ keys.reserve(luaL_len(L, index));
+ do
+ {
+ auto arraykeytype{ lua_type(L, -2) };
+ switch (arraykeytype)
+ {
+ case LUA_TNUMBER:
+ {
+ int isint;
+ lua_Integer intkey{ lua_tointegerx(L, -2, &isint) };
+ if (! isint)
+ {
+ // key isn't an integer - this doesn't fit our LLSD
+ // array constraints
+ return luaL_error(L, "Expected integer array key, got %f instead",
+ lua_tonumber(L, -2));
+ }
+ if (intkey < 1)
+ {
+ return luaL_error(L, "array key %d out of bounds", int(intkey));
+ }
+
+ keys.push_back(LLSD::Integer(intkey));
+ break;
+ }
+
+ case LUA_TSTRING:
+ // break out strings specially to report the value
+ return luaL_error(L, "Cannot convert string array key '%s' to LLSD",
+ lua_tostring(L, -2));
+
+ default:
+ return luaL_error(L, "Cannot convert %s array key to LLSD",
+ lua_typename(L, arraykeytype));
+ }
+
+ // remove value, keep key for next iteration
+ lua_pop(L, 1);
+ } while (lua_next(L, index) != 0);
+ popper.disarm();
+ // Table keys are all integers: are they reasonable integers?
+ // Arbitrary max: may bite us, but more likely to protect us
+ size_t array_max{ 10000 };
+ if (keys.size() > array_max)
+ {
+ return luaL_error(L, "Conversion from Lua to LLSD array limited to %d entries",
+ int(array_max));
+ }
+ // We know the smallest key is >= 1. Check the largest. We also
+ // know the vector is NOT empty, else we wouldn't have gotten here.
+ std::sort(keys.begin(), keys.end());
+ LLSD::Integer highkey = *keys.rbegin();
+ if ((highkey - LLSD::Integer(keys.size())) > 100)
+ {
+ // Looks like we've gone beyond intentional array gaps into
+ // crazy key territory.
+ return luaL_error(L, "Gaps in Lua table too large for conversion to LLSD array");
+ }
+ // right away expand the result array to the size we'll need
+ LLSD result{ LLSD::emptyArray() };
+ result[highkey - 1] = LLSD();
+ // Traverse the table again, and this time populate result array.
+ lua_pushnil(L); // first key
+ while (lua_next(L, index))
+ {
+ // key at stack index -2, value at index -1
+ // We've already validated lua_tointegerx() for each key.
+ // Don't forget to subtract 1 from Lua key for LLSD subscript!
+ result[LLSD::Integer(lua_tointeger(L, -2)) - 1] = lua_tollsd(L, -1);
+ // remove value, keep key for next iteration
+ lua_pop(L, 1);
+ }
+ return result;
+ }
+
+ case LUA_TSTRING:
+ {
+ // First Lua key is a string: try to convert table to LLSD map
+ LLSD result{ LLSD::emptyMap() };
+ do
+ {
+ auto mapkeytype{ lua_type(L, -2) };
+ if (mapkeytype != LUA_TSTRING)
+ {
+ return luaL_error(L, "Cannot convert %s map key to LLSD",
+ lua_typename(L, mapkeytype));
+ }
+
+ result[lua_tostdstring(L, -2)] = lua_tollsd(L, -1);
+ // remove value, keep key for next iteration
+ lua_pop(L, 1);
+ } while (lua_next(L, index) != 0);
+ popper.disarm();
+ return result;
+ }
+
+ default:
+ // First Lua key isn't number or string: sorry
+ return luaL_error(L, "Cannot convert %s table key to LLSD",
+ lua_typename(L, firstkeytype));
+ }
+ }
+
+ default:
+ // Other Lua entities (e.g. function, C function, light userdata,
+ // thread, userdata) are not convertible to LLSD, indicating a coding
+ // error in the caller.
+ return luaL_error(L, "Cannot convert type %s to LLSD", luaL_typename(L, index));
+ }
+}
+
+// By analogy with existing lua_pushmumble() functions, push onto state L's
+// stack a Lua object corresponding to the passed LLSD object.
+void lua_pushllsd(lua_State* L, const LLSD& data)
+{
+ // might need 2 slots for array or map
+ luaL_checkstack(L, 2, nullptr);
+ switch (data.type())
+ {
+ case LLSD::TypeUndefined:
+ lua_pushnil(L);
+ break;
+
+ case LLSD::TypeBoolean:
+ lua_pushboolean(L, data.asBoolean());
+ break;
+
+ case LLSD::TypeInteger:
+ lua_pushinteger(L, data.asInteger());
+ break;
+
+ case LLSD::TypeReal:
+ lua_pushnumber(L, data.asReal());
+ break;
+
+ case LLSD::TypeBinary:
+ {
+ auto binary{ data.asBinary() };
+ std::memcpy(lua_newuserdata(L, binary.size()),
+ binary.data(), binary.size());
+ break;
+ }
+
+ case LLSD::TypeMap:
+ {
+ // push a new table with space for our non-array keys
+ lua_createtable(L, 0, data.size());
+ for (const auto& pair: llsd::inMap(data))
+ {
+ // push value -- so now table is at -2, value at -1
+ lua_pushllsd(L, pair.second);
+ // pop value, assign to table[key]
+ lua_setfield(L, -2, pair.first.c_str());
+ }
+ break;
+ }
+
+ case LLSD::TypeArray:
+ {
+ // push a new table with space for array entries
+ lua_createtable(L, data.size(), 0);
+ lua_Integer key{ 0 };
+ for (const auto& item: llsd::inArray(data))
+ {
+ // push new array value: table at -2, value at -1
+ lua_pushllsd(L, item);
+ // pop value, assign table[key] = value
+ lua_seti(L, -2, ++key);
+ }
+ break;
+ }
+
+ case LLSD::TypeString:
+ case LLSD::TypeUUID:
+ case LLSD::TypeDate:
+ case LLSD::TypeURI:
+ default:
+ {
+ lua_pushstdstring(L, data.asString());
+ break;
+ }
+ }
+}
diff --git a/indra/newview/llluamanager.h b/indra/newview/llluamanager.h
new file mode 100644
index 0000000000..08d9876ce2
--- /dev/null
+++ b/indra/newview/llluamanager.h
@@ -0,0 +1,45 @@
+/**
+ * @file llluamanager.h
+ * @brief classes and functions for interfacing with LUA.
+ *
+ * $LicenseInfo:firstyear=2023&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2023, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#ifndef LL_LLLUAMANAGER_H
+#define LL_LLLUAMANAGER_H
+
+#include <functional>
+#include <string>
+
+class LLLUAmanager
+{
+public:
+ typedef std::function<void(std::string msg)> script_finished_fn;
+
+ static void runScriptFile(const std::string &filename, script_finished_fn cb = script_finished_fn());
+ static void runScriptLine(const std::string &cmd, script_finished_fn cb = script_finished_fn());
+
+ static void runScriptOnLogin();
+};
+
+
+#endif
diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp
index ad87fca25b..9edbeaf464 100644
--- a/indra/newview/llstartup.cpp
+++ b/indra/newview/llstartup.cpp
@@ -207,6 +207,7 @@
#include "llstacktrace.h"
#include "threadpool.h"
+#include "llluamanager.h"
#include "llperfstats.h"
@@ -2396,6 +2397,8 @@ bool idle_startup()
LLPerfStats::StatsRecorder::setAutotuneInit();
+ LLLUAmanager::runScriptOnLogin();
+
return TRUE;
}
diff --git a/indra/newview/lltoolplacer.cpp b/indra/newview/lltoolplacer.cpp
index 7cdd7cc5c8..a4e6d20181 100644
--- a/indra/newview/lltoolplacer.cpp
+++ b/indra/newview/lltoolplacer.cpp
@@ -164,13 +164,17 @@ BOOL LLToolPlacer::addObject( LLPCode pcode, S32 x, S32 y, U8 use_physics )
BOOL b_hit_land = FALSE;
S32 hit_face = -1;
LLViewerObject* hit_obj = NULL;
- U8 state = 0;
BOOL success = raycastForNewObjPos( x, y, &hit_obj, &hit_face, &b_hit_land, &ray_start_region, &ray_end_region, &regionp );
if( !success )
{
return FALSE;
}
+ return rezNewObject(pcode,hit_obj, hit_face, b_hit_land, ray_start_region, ray_end_region, regionp, use_physics);
+}
+BOOL LLToolPlacer::rezNewObject(LLPCode pcode, LLViewerObject * hit_obj, S32 hit_face, BOOL b_hit_land, LLVector3 ray_start_region,
+ LLVector3 ray_end_region, LLViewerRegion* regionp, U8 use_physics)
+{
if( hit_obj && (hit_obj->isAvatar() || hit_obj->isAttachment()) )
{
// Can't create objects on avatars or attachments
@@ -194,6 +198,8 @@ BOOL LLToolPlacer::addObject( LLPCode pcode, S32 x, S32 y, U8 use_physics )
U8 material = LL_MCODE_WOOD;
BOOL create_selected = FALSE;
LLVolumeParams volume_params;
+
+ U8 state = 0;
switch (pcode)
{
diff --git a/indra/newview/lltoolplacer.h b/indra/newview/lltoolplacer.h
index ad59cb0daa..d20d682710 100644
--- a/indra/newview/lltoolplacer.h
+++ b/indra/newview/lltoolplacer.h
@@ -50,12 +50,16 @@ public:
static void setObjectType( LLPCode type ) { sObjectType = type; }
static LLPCode getObjectType() { return sObjectType; }
+ static BOOL addObject(LLPCode pcode, S32 x, S32 y, U8 use_physics);
+ static BOOL rezNewObject(LLPCode pcode, LLViewerObject* hit_obj, S32 hit_face, BOOL b_hit_land, LLVector3 ray_start_region,
+ LLVector3 ray_end_region, LLViewerRegion *regionp, U8 use_physics);
+
protected:
static LLPCode sObjectType;
private:
- BOOL addObject( LLPCode pcode, S32 x, S32 y, U8 use_physics );
- BOOL raycastForNewObjPos( S32 x, S32 y, LLViewerObject** hit_obj, S32* hit_face,
+
+ static BOOL raycastForNewObjPos(S32 x, S32 y, LLViewerObject **hit_obj, S32 *hit_face,
BOOL* b_hit_land, LLVector3* ray_start_region, LLVector3* ray_end_region, LLViewerRegion** region );
BOOL addDuplicate(S32 x, S32 y);
};
diff --git a/indra/newview/lluilistener.cpp b/indra/newview/lluilistener.cpp
index 956f5cf187..97a2be6aa3 100644
--- a/indra/newview/lluilistener.cpp
+++ b/indra/newview/lluilistener.cpp
@@ -67,7 +67,7 @@ void LLUIListener::call(const LLSD& event) const
// API: we provide no reply. Therefore, a typo in the script will
// provide no feedback whatsoever to that script. To rub the coder's
// nose in such an error, crump rather than quietly ignoring it.
- LL_ERRS("LLUIListener") << "function '" << event["function"] << "' not found" << LL_ENDL;
+ LL_WARNS("LLUIListener") << "function '" << event["function"] << "' not found" << LL_ENDL;
}
else
{
diff --git a/indra/newview/lluilistener.h b/indra/newview/lluilistener.h
index 08724024dc..e53984dae2 100644
--- a/indra/newview/lluilistener.h
+++ b/indra/newview/lluilistener.h
@@ -39,7 +39,8 @@ class LLUIListener: public LLEventAPI
public:
LLUIListener();
-private:
+// FIXME These fields are intended to be private, changed here to support very hacky code in llluamanager.cpp
+public:
void call(const LLSD& event) const;
void getValue(const LLSD&event) const;
};
diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp
index 15b95d70a9..1cc584b36f 100644
--- a/indra/newview/llviewerfloaterreg.cpp
+++ b/indra/newview/llviewerfloaterreg.cpp
@@ -92,6 +92,7 @@
#include "llfloaterlandholdings.h"
#include "llfloaterlinkreplace.h"
#include "llfloaterloadprefpreset.h"
+#include "llfloaterluadebug.h"
#include "llfloatermap.h"
#include "llfloatermarketplacelistings.h"
#include "llfloatermediasettings.h"
@@ -391,6 +392,8 @@ void LLViewerFloaterReg::registerFloaters()
LLFloaterReg::add("land_holdings", "floater_land_holdings.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterLandHoldings>);
LLFloaterReg::add("linkreplace", "floater_linkreplace.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterLinkReplace>);
LLFloaterReg::add("load_pref_preset", "floater_load_pref_preset.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterLoadPrefPreset>);
+
+ LLFloaterReg::add("lua_debug", "floater_lua_debug.xml", (LLFloaterBuildFunc) &LLFloaterReg::build<LLFloaterLUADebug>);
LLFloaterReg::add("mem_leaking", "floater_mem_leaking.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterMemLeak>);
diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp
index 8686fad3e6..04c0c1eb17 100644
--- a/indra/newview/llviewermenu.cpp
+++ b/indra/newview/llviewermenu.cpp
@@ -171,6 +171,8 @@ extern BOOL gShaderProfileFrame;
// Globals
//
+LLUIListener sUIListener;
+
LLMenuBarGL *gMenuBarView = NULL;
LLViewerMenuHolderGL *gMenuHolder = NULL;
LLMenuGL *gPopupMenuView = NULL;
@@ -348,8 +350,6 @@ public:
static LLMenuParcelObserver* gMenuParcelObserver = NULL;
-static LLUIListener sUIListener;
-
LLMenuParcelObserver::LLMenuParcelObserver()
{
LLViewerParcelMgr::getInstance()->addObserver(this);
@@ -8893,71 +8893,80 @@ class LLToolsSelectTool : public view_listener_t
};
/// WINDLIGHT callbacks
-class LLWorldEnvSettings : public view_listener_t
-{
- void defocusEnvFloaters()
+void defocusEnvFloaters()
+{
+ // currently there is only one instance of each floater
+ std::vector<std::string> env_floaters_names = {"env_edit_extdaycycle", "env_fixed_environmentent_water",
+ "env_fixed_environmentent_sky"};
+ for (std::vector<std::string>::const_iterator it = env_floaters_names.begin(); it != env_floaters_names.end(); ++it)
{
- //currently there is only one instance of each floater
- std::vector<std::string> env_floaters_names = { "env_edit_extdaycycle", "env_fixed_environmentent_water", "env_fixed_environmentent_sky" };
- for (std::vector<std::string>::const_iterator it = env_floaters_names.begin(); it != env_floaters_names.end(); ++it)
+ LLFloater *env_floater = LLFloaterReg::findTypedInstance<LLFloater>(*it);
+ if (env_floater)
{
- LLFloater* env_floater = LLFloaterReg::findTypedInstance<LLFloater>(*it);
- if (env_floater)
- {
- env_floater->setFocus(FALSE);
- }
+ env_floater->setFocus(FALSE);
}
}
+}
+bool handle_env_setting_event(std::string event_name)
+{
+ if (event_name == "sunrise")
+ {
+ LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::KNOWN_SKY_SUNRISE,
+ LLEnvironment::TRANSITION_INSTANT);
+ LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT);
+ defocusEnvFloaters();
+ }
+ else if (event_name == "noon")
+ {
+ LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::KNOWN_SKY_MIDDAY,
+ LLEnvironment::TRANSITION_INSTANT);
+ LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT);
+ defocusEnvFloaters();
+ }
+ else if (event_name == "sunset")
+ {
+ LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::KNOWN_SKY_SUNSET,
+ LLEnvironment::TRANSITION_INSTANT);
+ LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT);
+ defocusEnvFloaters();
+ }
+ else if (event_name == "midnight")
+ {
+ LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::KNOWN_SKY_MIDNIGHT,
+ LLEnvironment::TRANSITION_INSTANT);
+ LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT);
+ defocusEnvFloaters();
+ }
+ else if (event_name == "region")
+ {
+ LLEnvironment::instance().clearEnvironment(LLEnvironment::ENV_LOCAL);
+ LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT);
+ defocusEnvFloaters();
+ }
+ else if (event_name == "pause_clouds")
+ {
+ if (LLEnvironment::instance().isCloudScrollPaused())
+ LLEnvironment::instance().resumeCloudScroll();
+ else
+ LLEnvironment::instance().pauseCloudScroll();
+ }
+ else if (event_name == "adjust_tool")
+ {
+ LLFloaterReg::showInstance("env_adjust_snapshot");
+ }
+ else if (event_name == "my_environs")
+ {
+ LLFloaterReg::showInstance("my_environments");
+ }
+ return true;
+}
+
+class LLWorldEnvSettings : public view_listener_t
+{
bool handleEvent(const LLSD& userdata)
{
- std::string event_name = userdata.asString();
-
- if (event_name == "sunrise")
- {
- LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::KNOWN_SKY_SUNRISE, LLEnvironment::TRANSITION_INSTANT);
- LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT);
- defocusEnvFloaters();
- }
- else if (event_name == "noon")
- {
- LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::KNOWN_SKY_MIDDAY, LLEnvironment::TRANSITION_INSTANT);
- LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT);
- defocusEnvFloaters();
- }
- else if (event_name == "sunset")
- {
- LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::KNOWN_SKY_SUNSET, LLEnvironment::TRANSITION_INSTANT);
- LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT);
- defocusEnvFloaters();
- }
- else if (event_name == "midnight")
- {
- LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::KNOWN_SKY_MIDNIGHT, LLEnvironment::TRANSITION_INSTANT);
- LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT);
- defocusEnvFloaters();
- }
- else if (event_name == "region")
- {
- LLEnvironment::instance().clearEnvironment(LLEnvironment::ENV_LOCAL);
- LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT);
- defocusEnvFloaters();
- }
- else if (event_name == "pause_clouds")
- {
- if (LLEnvironment::instance().isCloudScrollPaused())
- LLEnvironment::instance().resumeCloudScroll();
- else
- LLEnvironment::instance().pauseCloudScroll();
- }
- else if (event_name == "adjust_tool")
- {
- LLFloaterReg::showInstance("env_adjust_snapshot");
- }
- else if (event_name == "my_environs")
- {
- LLFloaterReg::showInstance("my_environments");
- }
+ handle_env_setting_event(userdata.asString());
return true;
}
diff --git a/indra/newview/llviewermenu.h b/indra/newview/llviewermenu.h
index a90b32c984..b17a5d8b96 100644
--- a/indra/newview/llviewermenu.h
+++ b/indra/newview/llviewermenu.h
@@ -140,6 +140,7 @@ void handle_give_money_dialog();
bool enable_pay_object();
bool enable_buy_object();
bool handle_go_to();
+bool handle_env_setting_event(std::string event_name);
// Export to XML or Collada
void handle_export_selected( void * );
diff --git a/indra/newview/llviewermenufile.cpp b/indra/newview/llviewermenufile.cpp
index 17e89e1850..15088244f9 100644
--- a/indra/newview/llviewermenufile.cpp
+++ b/indra/newview/llviewermenufile.cpp
@@ -741,6 +741,20 @@ class LLFileEnableCloseAllWindows : public view_listener_t
}
};
+void close_all_windows()
+{
+ bool app_quitting = false;
+ gFloaterView->closeAllChildren(app_quitting);
+ LLFloaterSnapshot *floater_snapshot = LLFloaterSnapshot::findInstance();
+ if (floater_snapshot)
+ floater_snapshot->closeFloater(app_quitting);
+ LLFloaterSimpleOutfitSnapshot *floater_outfit_snapshot = LLFloaterSimpleOutfitSnapshot::findInstance();
+ if (floater_outfit_snapshot)
+ floater_outfit_snapshot->closeFloater(app_quitting);
+ if (gMenuHolder)
+ gMenuHolder->hideMenus();
+}
+
class LLFileCloseAllWindows : public view_listener_t
{
bool handleEvent(const LLSD& userdata)
diff --git a/indra/newview/llviewermenufile.h b/indra/newview/llviewermenufile.h
index 61572b9996..fd13eef8b7 100644
--- a/indra/newview/llviewermenufile.h
+++ b/indra/newview/llviewermenufile.h
@@ -72,6 +72,8 @@ void assign_defaults_and_show_upload_message(
const std::string& display_name,
std::string& description);
+void close_all_windows();
+
//consider moving all file pickers below to more suitable place
class LLFilePickerThread : public LLThread
{ //multi-threaded file picker (runs system specific file picker in background and calls "notify" from main thread)
diff --git a/indra/newview/skins/default/xui/en/floater_lua_debug.xml b/indra/newview/skins/default/xui/en/floater_lua_debug.xml
new file mode 100644
index 0000000000..f03739f7c2
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/floater_lua_debug.xml
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<floater
+ can_minimize="false"
+ can_resize="false"
+ can_close="true"
+ bevel_style="in"
+ height="220"
+ layout="topleft"
+ name="LUA debug"
+ save_rect="true"
+ title="LUA DEBUG"
+ single_instance="true"
+ width="535">
+ <text
+ type="string"
+ length="1"
+ follows="left|bottom"
+ font="SansSerif"
+ height="30"
+ layout="topleft"
+ left="10"
+ left_delta="10"
+ name="editor_path_label"
+ top="10"
+ width="100">
+ LUA string:
+ </text>
+ <line_editor
+ border_style="line"
+ border_thickness="1"
+ follows="left|bottom"
+ font="SansSerif"
+ height="20"
+ layout="topleft"
+ left="10"
+ max_length_bytes="300"
+ name="lua_cmd"
+ select_on_focus="true"
+ top_delta="30"
+ width="435" />
+ <button
+ follows="left|bottom"
+ height="25"
+ label="Execute"
+ layout="topleft"
+ left_pad="5"
+ name="execute_btn"
+ top_delta="-2"
+ width="75" />
+
+ <text_editor
+ enabled="false"
+ left="10"
+ height="95"
+ layout="topleft"
+ name="result_text"
+ follows="left|top"
+ max_length="65536"
+ width="515"
+ top_delta="40"
+ word_wrap="true" />
+ <text
+ type="string"
+ length="1"
+ follows="left|bottom"
+ font="SansSerif"
+ height="30"
+ layout="topleft"
+ left="10"
+ name="path_label"
+ top_pad="15"
+ width="100">
+ File Path:
+ </text>
+ <line_editor
+ border_style="line"
+ enabled="false"
+ border_thickness="1"
+ follows="left|bottom"
+ font="SansSerif"
+ height="20"
+ layout="topleft"
+ left_delta="65"
+ max_length_bytes="300"
+ name="script_path"
+ select_on_focus="true"
+ top_delta="-2"
+ width="320" />
+ <button
+ follows="left|bottom"
+ height="25"
+ label="Browse..."
+ label_selected="Browse..."
+ layout="topleft"
+ left_pad="5"
+ name="browse_btn"
+ top_delta="-2"
+ width="70" />
+ <button
+ follows="left|bottom"
+ height="25"
+ label="Run"
+ label_selected="Run"
+ layout="topleft"
+ left_pad="5"
+ name="run_btn"
+ width="50" />
+</floater>
diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml
index 810d3fddd5..89643189b5 100644
--- a/indra/newview/skins/default/xui/en/menu_viewer.xml
+++ b/indra/newview/skins/default/xui/en/menu_viewer.xml
@@ -2485,6 +2485,17 @@ function="World.EnvPreset"
</menu_item_call>
<menu_item_separator/>
+ <menu_item_check
+ label="LUA Debug Console"
+ name="LUA Debug Console">
+ <menu_item_check.on_check
+ function="Floater.Visible"
+ parameter="lua_debug" />
+ <menu_item_check.on_click
+ function="Floater.Toggle"
+ parameter="lua_debug" />
+ </menu_item_check>
+ <menu_item_separator/>
<menu_item_call
label="Region Info to Debug Console"
diff --git a/indra/newview/tests/llluamanager_test.cpp b/indra/newview/tests/llluamanager_test.cpp
new file mode 100644
index 0000000000..e5e06b095c
--- /dev/null
+++ b/indra/newview/tests/llluamanager_test.cpp
@@ -0,0 +1,101 @@
+/**
+ * @file llluamanager_test.cpp
+ * @author Nat Goodspeed
+ * @date 2023-09-28
+ * @brief Test for llluamanager.
+ *
+ * $LicenseInfo:firstyear=2023&license=viewerlgpl$
+ * Copyright (c) 2023, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+//#include "llviewerprecompiledheaders.h"
+// associated header
+#include "../newview/llluamanager.h"
+// STL headers
+// std headers
+// external library headers
+// other Linden headers
+#include "../test/lltut.h"
+#include "llapp.h"
+#include "llevents.h"
+#include "lleventcoro.h"
+#include "stringize.h"
+#include "../llcommon/tests/StringVec.h"
+
+class LLTestApp : public LLApp
+{
+public:
+ bool init() override { return true; }
+ bool cleanup() override { return true; }
+ bool frame() override { return true; }
+};
+
+/*****************************************************************************
+* TUT
+*****************************************************************************/
+namespace tut
+{
+ struct llluamanager_data
+ {
+ // We need an LLApp instance because LLLUAmanager uses coroutines,
+ // which suspend, and when a coroutine suspends it checks LLApp state,
+ // and if it's not APP_STATUS_RUNNING the coroutine terminates.
+ LLTestApp mApp;
+ };
+ typedef test_group<llluamanager_data> llluamanager_group;
+ typedef llluamanager_group::object object;
+ llluamanager_group llluamanagergrp("llluamanager");
+
+ template<> template<>
+ void object::test<1>()
+ {
+ set_test_name("test post_on(), listen_events(), await_event()");
+ StringVec posts;
+ LLEventStream replypump("testpump");
+ LLTempBoundListener conn(
+ replypump.listen("test<1>",
+ [&posts](const LLSD& post)
+ {
+ posts.push_back(post.asString());
+ return false;
+ }));
+ const std::string lua(
+ "-- test post_on,listen_events,await_event\n"
+ "post_on('testpump', 'entry')\n"
+ "callback = function(pump, data)\n"
+ " -- just echo the data we received\n"
+ " post_on('testpump', data)\n"
+ "end\n"
+ "post_on('testpump', 'listen_events()')\n"
+ "replypump, cmdpump = listen_events(callback)\n"
+ "post_on('testpump', replypump)\n"
+ "post_on('testpump', 'await_event()')\n"
+ "await_event(replypump)\n"
+ "post_on('testpump', 'exit')\n"
+ );
+ LLLUAmanager::runScriptLine(lua);
+ StringVec expected{
+ "entry",
+ "listen_events()",
+ "",
+ "await_event()",
+ "message",
+ "exit"
+ };
+ for (int i = 0; i < 10 && posts.size() <= 2 && posts[2].empty(); ++i)
+ {
+ llcoro::suspend();
+ }
+ expected[2] = posts.at(2);
+ LL_DEBUGS() << "Found pumpname '" << expected[2] << "'" << LL_ENDL;
+ LLEventPump& luapump{ LLEventPumps::instance().obtain(expected[2]) };
+ LL_DEBUGS() << "Found pump '" << luapump.getName() << "', type '"
+ << LLError::Log::classname(luapump)
+ << "': post('" << expected[4] << "')" << LL_ENDL;
+ luapump.post(expected[4]);
+ llcoro::suspend();
+ ensure_equals("post_on() sequence", posts, expected);
+ }
+} // namespace tut
diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py
index 3a7c7d7f46..83d88ad21d 100755
--- a/indra/newview/viewer_manifest.py
+++ b/indra/newview/viewer_manifest.py
@@ -523,6 +523,9 @@ class WindowsManifest(ViewerManifest):
# Uriparser
self.path("uriparser.dll")
+ # lua libs
+ self.path("lua54.dll")
+
# These need to be installed as a SxS assembly, currently a 'private' assembly.
# See http://msdn.microsoft.com/en-us/library/ms235291(VS.80).aspx
self.path("msvcp140.dll")
diff --git a/indra/test/io.cpp b/indra/test/io.cpp
index 40243a8ad6..027db50693 100644
--- a/indra/test/io.cpp
+++ b/indra/test/io.cpp
@@ -45,6 +45,7 @@
#include "llcommon.h"
#include "lluuid.h"
#include "llinstantmessage.h"
+#include "stringize.h"
namespace tut
{
@@ -1116,6 +1117,9 @@ namespace tut
template<> template<>
void fitness_test_object::test<5>()
{
+ skip("Test is strongly timing dependent, "
+ "and on slow CI machines it fails way too often.");
+ const int retries = 100;
// Set up the server
LLPumpIO::chain_t chain;
typedef LLCloneIOFactory<LLIOSleeper> sleeper_t;
@@ -1129,9 +1133,12 @@ namespace tut
chain.push_back(LLIOPipe::ptr_t(server));
mPump->addChain(chain, NEVER_CHAIN_EXPIRY_SECS);
// We need to tickle the pump a little to set up the listen()
- pump_loop(mPump, 0.1f);
+ for (int retry = 0; mPump->runningChains() < 1 && retry < retries; ++retry)
+ {
+ pump_loop(mPump, 0.1f);
+ }
U32 count = mPump->runningChains();
- ensure_equals("server chain onboard", count, 1);
+ ensure_equals("server chain 1 onboard", count, 1);
LL_DEBUGS() << "** Server is up." << LL_ENDL;
// Set up the client
@@ -1140,9 +1147,12 @@ namespace tut
bool connected = client->blockingConnect(server_host);
ensure("Connected to server", connected);
LL_DEBUGS() << "connected" << LL_ENDL;
- pump_loop(mPump,0.1f);
+ for (int retry = 0; mPump->runningChains() < 2 && retry < retries; ++retry)
+ {
+ pump_loop(mPump,0.1f);
+ }
count = mPump->runningChains();
- ensure_equals("server chain onboard", count, 2);
+ ensure_equals("server chain 2 onboard", count, 2);
LL_DEBUGS() << "** Client is connected." << LL_ENDL;
// We have connected, since the socket reader does not block,
@@ -1156,20 +1166,32 @@ namespace tut
chain.clear();
// pump for a bit and make sure all 3 chains are running
- pump_loop(mPump,0.1f);
+ for (int retry = 0; mPump->runningChains() < 3 && retry < retries; ++retry)
+ {
+ pump_loop(mPump, 0.1f);
+ }
count = mPump->runningChains();
- // ensure_equals("client chain onboard", count, 3); commented out because it fails frequently - appears to be timing sensitive
+ ensure_equals("client chain onboard", count, 3);
LL_DEBUGS() << "** request should have been sent." << LL_ENDL;
// pump for long enough the the client socket closes, and the
// server socket should not be closed yet.
- pump_loop(mPump,0.2f);
+ for (int retry = 0; mPump->runningChains() == 3 && retry < retries; ++retry)
+ {
+ pump_loop(mPump, 0.1f);
+ }
+ // We used to test for count == 2 here, but on a slow test machine it
+ // can happen that not just one but two chains close before we reach
+ // this point.
count = mPump->runningChains();
- ensure_equals("client chain timed out ", count, 2);
+ ensure(stringize("client chain timed out: count ", count), count < 3);
LL_DEBUGS() << "** client chain should be closed." << LL_ENDL;
// At this point, the socket should be closed by the timeout
- pump_loop(mPump,1.0f);
+ for (int retry = 0; mPump->runningChains() > 1 && retry < retries; ++retry)
+ {
+ pump_loop(mPump, 0.1f);
+ }
count = mPump->runningChains();
ensure_equals("accepted socked close", count, 1);
LL_DEBUGS() << "** Sleeper should have timed out.." << LL_ENDL;
diff --git a/scripts/lua/avatar.lua b/scripts/lua/avatar.lua
new file mode 100644
index 0000000000..7c419a740c
--- /dev/null
+++ b/scripts/lua/avatar.lua
@@ -0,0 +1,14 @@
+function call_once_func()
+ run_ui_command("World.EnvSettings", "midnight")
+ sleep(1)
+ run_ui_command("World.EnvSettings", "noon")
+ sleep(1)
+ wear_by_name("* AVL")
+ run_ui_command("Avatar.ResetSelfSkeletonAndAnimations")
+ sleep(5)
+ wear_by_name("* Elephant")
+ sleep(5)
+ play_animation("Elephant_Fly");
+ sleep(5)
+ play_animation("Elephant_Fly",1);
+end \ No newline at end of file
diff --git a/scripts/lua/demo.lua b/scripts/lua/demo.lua
new file mode 100644
index 0000000000..dc2d9ef669
--- /dev/null
+++ b/scripts/lua/demo.lua
@@ -0,0 +1,120 @@
+function popup_and_wait_ok(message)
+ args = {{"MESSAGE", message}}
+ notif_response = nil
+ show_notification("GenericAlertOK", args, "notif_response")
+ while not notif_response do
+ sleep(0.2)
+ end
+
+ local response = notif_response
+ return response
+end
+
+function demo_environment()
+ popup_and_wait_ok("Change Environment")
+ run_ui_command("World.EnvSettings", "midnight")
+ sleep(2)
+ run_ui_command("World.EnvSettings", "sunrise")
+ sleep(2)
+ run_ui_command("World.EnvSettings", "noon")
+ sleep(2)
+end
+
+function demo_avatar()
+ popup_and_wait_ok("Change Avatar")
+ wear_by_name("Greg")
+ run_ui_command("Avatar.ResetSelfSkeletonAndAnimations")
+ sleep(8)
+
+ wear_by_name("Petrol Sue")
+ sleep(8)
+
+ run_ui_command("Self.ToggleSitStand")
+ sleep(2)
+ run_ui_command("Self.ToggleSitStand")
+ sleep(2)
+
+ run_ui_command("View.ZoomOut")
+ run_ui_command("View.ZoomOut")
+ run_ui_command("EditShape")
+ sleep(6)
+ close_floater("appearance")
+
+end
+
+function demo_ui()
+
+
+ -- adding items to 'Build' menu
+ -- popup_and_wait_ok("Extend UI")
+
+ popup_and_wait_ok("UI interaction")
+ open_floater("inventory")
+ open_floater("preferences")
+ open_floater("nearby_chat")
+ nearby_chat_send("Hello World!")
+
+ sleep(5)
+ close_all_floaters()
+
+
+ notif_response = nil
+ args = {{"MESSAGE", "Customize the UI now?"}}
+ show_notification("GenericAlertYesCancel", args, "notif_response")
+ while not notif_response do
+ sleep(0.2)
+ end
+ if notif_response ~= 0 then
+ popup_and_wait_ok("Exiting")
+ return
+ end
+
+ menu_name = "BuildTools"
+ add_menu_separator(menu_name)
+
+ params = {{"name", "user_sit"}, {"label", "Sit!"},
+ {"function", "Self.ToggleSitStand"}}
+
+ add_menu_item(menu_name, params)
+
+ params = {{"name", "user_midnight"}, {"label", "Set night"},
+ {"function", "World.EnvSettings"}, {"parameter", "midnight"}}
+
+ add_menu_item(menu_name, params)
+
+ -- adding new custom menu
+ new_menu_name = "user_menu"
+ params = {{"name", new_menu_name}, {"label", "My Secret Menu"}, {"tear_off", "true"}}
+ add_menu(params)
+
+ -- adding new item to the new menu
+ params = {{"name", "user_debug"}, {"label", "Console"},
+ {"function", "Advanced.ToggleConsole"}, {"parameter", "debug"}}
+
+ add_menu_item(new_menu_name, params)
+
+ -- adding new branch
+ new_branch = "user_floaters"
+ params = {{"name", new_branch}, {"label", "Open Floater"}, {"tear_off", "true"}}
+ add_branch(new_menu_name, params)
+
+ -- adding items to the branch
+ params = {{"name", "user_permissions"}, {"label", "Default permissions"},
+ {"function", "Floater.ToggleOrBringToFront"}, {"parameter", "perms_default"}}
+
+ add_menu_item(new_branch, params)
+
+ params = {{"name", "user_beacons"}, {"label", "Beacons"},
+ {"function", "Floater.ToggleOrBringToFront"}, {"parameter", "beacons"}}
+
+ add_menu_item(new_branch, params)
+ sleep(5)
+
+end
+
+function call_once_func()
+
+ demo_environment()
+ demo_avatar()
+ demo_ui()
+end