Skip to content

Commit 838ca02

Browse files
committed
fix: fix client command registration
1 parent af52245 commit 838ca02

11 files changed

Lines changed: 349 additions & 30 deletions

File tree

src-client/lse/PluginManager.cpp

Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
#include "lse/PluginManager.h"
2+
3+
#include "legacy/engine/EngineManager.h"
4+
#include "legacy/engine/EngineOwnData.h"
5+
#include "ll/api/io/FileUtils.h" // IWYU pragma: keep
6+
#include "ll/api/mod/Mod.h"
7+
#include "ll/api/mod/ModManager.h"
8+
#include "ll/api/service/GamingStatus.h"
9+
#include "ll/api/utils/ErrorUtils.h"
10+
#include "ll/api/utils/StringUtils.h"
11+
#include "lse/Entry.h"
12+
#include "lse/Plugin.h"
13+
14+
#include <ScriptX/ScriptX.h>
15+
#include <exception>
16+
#include <filesystem>
17+
#include <memory>
18+
19+
#ifdef LSE_BACKEND_LUA
20+
21+
constexpr auto BaseLibFileName = "BaseLib.lua";
22+
constexpr auto PluginManagerName = "lse-lua";
23+
24+
#endif
25+
26+
#ifdef LSE_BACKEND_QUICKJS
27+
28+
constexpr auto BaseLibFileName = "BaseLib.js";
29+
constexpr auto PluginManagerName = "lse-quickjs";
30+
31+
#endif
32+
33+
#ifdef LSE_BACKEND_PYTHON
34+
35+
#include "legacy/main/PythonHelper.h"
36+
constexpr auto BaseLibFileName = "BaseLib.py";
37+
constexpr auto PluginManagerName = "lse-python";
38+
39+
#endif
40+
41+
#ifdef LSE_BACKEND_NODEJS
42+
43+
#include "legacy/main/NodeJsHelper.h"
44+
constexpr auto PluginManagerName = "lse-nodejs";
45+
46+
#endif
47+
48+
// Do not use legacy headers directly, otherwise there will be tons of errors.
49+
void BindAPIs(std::shared_ptr<ScriptEngine> engine);
50+
void LLSERemoveTimeTaskData(std::shared_ptr<ScriptEngine> engine);
51+
bool LLSERemoveAllEventListeners(std::shared_ptr<ScriptEngine> engine);
52+
bool LLSERemoveCmdRegister(std::shared_ptr<ScriptEngine> engine);
53+
bool LLSERemoveCmdCallback(std::shared_ptr<ScriptEngine> engine);
54+
bool LLSERemoveAllExportedFuncs(std::shared_ptr<ScriptEngine> engine);
55+
bool LLSECallEventsOnHotLoad(std::shared_ptr<ScriptEngine> engine);
56+
bool LLSECallEventsOnUnload(std::shared_ptr<ScriptEngine> engine);
57+
58+
namespace lse {
59+
60+
PluginManager::PluginManager() : ll::mod::ModManager(PluginManagerName) {}
61+
PluginManager::~PluginManager() = default;
62+
63+
ll::Expected<> PluginManager::load(ll::mod::Manifest manifest) {
64+
if (hasMod(manifest.name)) {
65+
return ll::makeStringError("Plugin has already loaded");
66+
}
67+
68+
auto plugin = std::make_shared<Plugin>(manifest);
69+
70+
return plugin->onLoad().transform([&, this] { addMod(manifest.name, plugin); });
71+
}
72+
73+
ll::Expected<> PluginManager::enable(std::string_view name) {
74+
#ifdef LSE_BACKEND_PYTHON
75+
auto& logger = lse::LegacyScriptEngine::getLogger();
76+
std::filesystem::path dirPath = ll::mod::getModsRoot() / manifest.name; // Plugin path
77+
std::string entryPath =
78+
PythonHelper::findEntryScript(ll::string_utils::u8str2str(dirPath.u8string())); // Plugin entry
79+
// if (entryPath.empty()) return false;
80+
// std::string pluginName = PythonHelper::getPluginPackageName(dirPath.string()); // Plugin name
81+
82+
// Run "pip install" if needed
83+
auto realPackageInstallDir = (std::filesystem::path(dirPath) / "site-packages").make_preferred();
84+
if (!std::filesystem::exists(realPackageInstallDir)) {
85+
std::string dependTmpFilePath =
86+
PythonHelper::getPluginPackDependencyFilePath(ll::string_utils::u8str2str(dirPath.u8string()));
87+
if (!dependTmpFilePath.empty()) {
88+
int exitCode = 0;
89+
logger.info(
90+
"Executing \"pip install\" for plugin {name}..."_tr(
91+
fmt::arg("name", ll::string_utils::u8str2str(dirPath.filename().u8string()))
92+
)
93+
);
94+
95+
if ((exitCode = PythonHelper::executePipCommand(
96+
"pip install -r \"" + dependTmpFilePath + "\" -t \""
97+
+ ll::string_utils::u8str2str(realPackageInstallDir.u8string()) + "\" --disable-pip-version-check "
98+
))
99+
== 0) {
100+
logger.info("Pip finished successfully."_tr());
101+
} else logger.error("Error occurred. Exit code: {code}"_tr(fmt::arg("code", exitCode)));
102+
103+
// remove temp dependency file after installation
104+
std::error_code ec;
105+
std::filesystem::remove(std::filesystem::path(dependTmpFilePath), ec);
106+
}
107+
}
108+
#endif
109+
#ifdef LSE_BACKEND_NODEJS
110+
auto& logger = lse::LegacyScriptEngine::getLogger();
111+
std::filesystem::path dirPath = ll::mod::getModsRoot() / manifest.name; // Plugin path
112+
// std::string entryPath = NodeJsHelper::findEntryScript(dirPath.string()); // Plugin entry
113+
// if (entryPath.empty()) return false;
114+
// std::string pluginName = NodeJsHelper::getPluginPackageName(dirPath.string()); // Plugin name
115+
116+
// Run "npm install" if needed
117+
if (NodeJsHelper::doesPluginPackHasDependency(ll::string_utils::u8str2str(dirPath.u8string()))
118+
&& !std::filesystem::exists(std::filesystem::path(dirPath) / "node_modules")) {
119+
int exitCode = 0;
120+
logger.info(
121+
"Executing \"npm install\" for plugin {name}..."_tr(
122+
fmt::arg("name", ll::string_utils::u8str2str(dirPath.filename().u8string()))
123+
)
124+
);
125+
if ((exitCode = NodeJsHelper::executeNpmCommand(
126+
{"install", "--omit=dev", "--no-fund"},
127+
ll::string_utils::u8str2str(dirPath.u8string())
128+
))
129+
!= 0) {
130+
logger.error("Error occurred. Exit code: {code}"_tr(fmt::arg("code", exitCode)));
131+
}
132+
}
133+
#endif
134+
auto plugin = std::static_pointer_cast<Plugin>(getMod(name));
135+
if (!plugin) {
136+
return ll::makeStringError("Plugin {0} not found"_tr(name));
137+
}
138+
auto manifest = plugin->getManifest();
139+
140+
auto scriptEngine = EngineManager::newEngine(manifest.name);
141+
142+
try {
143+
EngineScope engineScope(scriptEngine.get());
144+
145+
// Init plugin logger
146+
getEngineOwnData()->logger = ll::io::LoggerRegistry::getInstance().getOrCreate(manifest.name);
147+
148+
#ifdef LSE_BACKEND_PYTHON
149+
scriptEngine->eval("import sys as _llse_py_sys_module");
150+
std::error_code ec;
151+
152+
// add plugin-own site-packages to sys.path
153+
std::string pluginSitePackageFormatted = ll::string_utils::u8str2str(
154+
std::filesystem::canonical(realPackageInstallDir.make_preferred(), ec).u8string()
155+
);
156+
if (!ec) {
157+
scriptEngine->eval("_llse_py_sys_module.path.insert(0, r'" + pluginSitePackageFormatted + "')");
158+
}
159+
// add plugin source dir to sys.path
160+
std::string sourceDirFormatted =
161+
ll::string_utils::u8str2str(std::filesystem::canonical(dirPath.make_preferred()).u8string());
162+
scriptEngine->eval("_llse_py_sys_module.path.insert(0, r'" + sourceDirFormatted + "')");
163+
164+
// set __file__ and __name__
165+
std::string entryPathFormatted = ll::string_utils::u8str2str(
166+
std::filesystem::canonical(std::filesystem::path(entryPath).make_preferred()).u8string()
167+
);
168+
scriptEngine->set("__file__", entryPathFormatted);
169+
// engine->set("__name__", String::newString("__main__"));
170+
#endif
171+
172+
BindAPIs(scriptEngine);
173+
174+
#ifndef LSE_BACKEND_NODEJS // NodeJs backend load depends code in another place
175+
auto& self = LegacyScriptEngine::getInstance().getSelf();
176+
// Load BaseLib.
177+
auto baseLibPath = self.getModDir() / "baselib" / BaseLibFileName;
178+
auto baseLibContent = ll::file_utils::readFile(baseLibPath);
179+
if (!baseLibContent) {
180+
return ll::makeStringError("Failed to read BaseLib at {0}"_tr(baseLibPath.string()));
181+
}
182+
scriptEngine->eval(baseLibContent.value());
183+
#endif
184+
// Load the plugin entry.
185+
auto entryPath = plugin->getModDir() / manifest.entry;
186+
getEngineOwnData()->plugin = plugin;
187+
#ifdef LSE_BACKEND_PYTHON
188+
if (!PythonHelper::loadPluginCode(
189+
scriptEngine,
190+
ll::string_utils::u8str2str(entryPath.u8string()),
191+
ll::string_utils::u8str2str(dirPath.u8string())
192+
)) {
193+
return ll::makeStringError("Failed to load plugin code"_tr());
194+
}
195+
#endif
196+
#ifdef LSE_BACKEND_NODEJS
197+
if (!NodeJsHelper::loadPluginCode(
198+
scriptEngine,
199+
ll::string_utils::u8str2str(entryPath.u8string()),
200+
ll::string_utils::u8str2str(dirPath.u8string()),
201+
NodeJsHelper::isESModulesSystem(ll::string_utils::u8str2str(dirPath.u8string()))
202+
)) {
203+
return ll::makeStringError("Failed to load plugin code"_tr());
204+
}
205+
#endif
206+
#if (defined LSE_BACKEND_QUICKJS) || (defined LSE_BACKEND_LUA)
207+
// Try loadFile
208+
try {
209+
scriptEngine->loadFile(entryPath.u8string());
210+
} catch (const Exception&) {
211+
// loadFile failed, try eval
212+
auto pluginEntryContent = ll::file_utils::readFile(entryPath);
213+
if (!pluginEntryContent) {
214+
return ll::makeStringError("Failed to read plugin entry at {0}"_tr(entryPath.string()));
215+
}
216+
scriptEngine->eval(pluginEntryContent.value(), entryPath.u8string());
217+
}
218+
#endif
219+
if (ll::getGamingStatus() == ll::GamingStatus::Running) { // Is hot load
220+
LLSECallEventsOnHotLoad(scriptEngine);
221+
}
222+
ExitEngineScope exit;
223+
224+
if (auto res = plugin->onEnable(); !res) {
225+
return res;
226+
}
227+
228+
return {};
229+
} catch (const Exception& e) {
230+
if (scriptEngine) {
231+
auto error = [&] {
232+
EngineScope engineScope(scriptEngine.get());
233+
return ll::makeStringError(
234+
"Failed to enable plugin {0}: {1}\n{2}"_tr(manifest.name, e.message(), e.stacktrace())
235+
);
236+
}();
237+
238+
#ifndef LSE_BACKEND_NODEJS
239+
LLSERemoveTimeTaskData(scriptEngine);
240+
#endif
241+
LLSERemoveAllEventListeners(scriptEngine);
242+
LLSERemoveCmdRegister(scriptEngine);
243+
LLSERemoveCmdCallback(scriptEngine);
244+
LLSERemoveAllExportedFuncs(scriptEngine);
245+
246+
EngineOwnData::clearEngineObjects(scriptEngine);
247+
EngineManager::unregisterEngine(scriptEngine);
248+
#ifdef LSE_BACKEND_NODEJS
249+
NodeJsHelper::stopEngine(scriptEngine);
250+
#endif
251+
return error;
252+
} else {
253+
return ll::makeStringError("Failed to enable plugin {0}: {1}"_tr(manifest.name, "ScriptEngine is nullptr"));
254+
}
255+
}
256+
}
257+
258+
ll::Expected<> PluginManager::disable(std::string_view name) {
259+
try {
260+
auto scriptEngine = EngineManager::getEngine(std::string(name));
261+
262+
if (!scriptEngine) {
263+
return ll::makeStringError("Plugin {0} not found"_tr(name));
264+
}
265+
266+
{
267+
EngineScope scope(scriptEngine.get());
268+
LLSECallEventsOnUnload(scriptEngine);
269+
#ifndef LSE_BACKEND_NODEJS
270+
LLSERemoveTimeTaskData(scriptEngine);
271+
#endif
272+
LLSERemoveAllEventListeners(scriptEngine);
273+
LLSERemoveCmdRegister(scriptEngine);
274+
LLSERemoveCmdCallback(scriptEngine);
275+
LLSERemoveAllExportedFuncs(scriptEngine);
276+
EngineOwnData::clearEngineObjects(scriptEngine);
277+
}
278+
EngineManager::unregisterEngine(scriptEngine);
279+
#ifdef LSE_BACKEND_NODEJS
280+
NodeJsHelper::stopEngine(scriptEngine);
281+
#endif
282+
283+
if (auto res = std::static_pointer_cast<Plugin>(getMod(name))->onDisable(); !res) {
284+
return res;
285+
}
286+
return {};
287+
} catch (const Exception&) {
288+
return ll::makeStringError("Failed to disable plugin {0}: {1}"_tr(name, "Unknown script exception"));
289+
} catch (const std::exception& e) {
290+
return ll::makeStringError("Failed to disable plugin {0}: {1}"_tr(name, e.what()));
291+
}
292+
}
293+
294+
ll::Expected<> PluginManager::unload(std::string_view name) {
295+
if (auto res = std::static_pointer_cast<Plugin>(getMod(name))->onUnload(); !res) {
296+
return res;
297+
}
298+
eraseMod(name);
299+
return {};
300+
}
301+
302+
} // namespace lse

src-client/lse/PluginManager.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#pragma once
2+
#include "ll/api/Expected.h"
3+
#include "ll/api/mod/Manifest.h"
4+
#include "ll/api/mod/ModManager.h"
5+
6+
#include <string_view>
7+
8+
namespace lse {
9+
10+
class PluginManager final : public ll::mod::ModManager {
11+
public:
12+
PluginManager();
13+
~PluginManager() override;
14+
15+
private:
16+
ll::Expected<> load(ll::mod::Manifest manifest) override;
17+
ll::Expected<> unload(std::string_view name) override;
18+
ll::Expected<> enable(std::string_view name) override;
19+
ll::Expected<> disable(std::string_view name) override;
20+
};
21+
22+
} // namespace lse
Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,18 @@
1-
#include "PluginManager.h"
1+
#include "lse/PluginManager.h"
22

3-
#include "Entry.h"
4-
#include "Plugin.h"
53
#include "legacy/engine/EngineManager.h"
64
#include "legacy/engine/EngineOwnData.h"
7-
#include "ll/api/chrono/GameChrono.h"
8-
#include "ll/api/coro/CoroTask.h"
95
#include "ll/api/io/FileUtils.h" // IWYU pragma: keep
106
#include "ll/api/mod/Mod.h"
117
#include "ll/api/mod/ModManager.h"
128
#include "ll/api/service/GamingStatus.h"
13-
#include "ll/api/thread/ServerThreadExecutor.h"
149
#include "ll/api/utils/StringUtils.h"
10+
#include "lse/Entry.h"
11+
#include "lse/Plugin.h"
1512

1613
#include <ScriptX/ScriptX.h>
1714
#include <exception>
1815
#include <filesystem>
19-
#include <fmt/format.h>
2016
#include <memory>
2117

2218
#ifdef LSE_BACKEND_LUA
@@ -212,7 +208,7 @@ ll::Expected<> PluginManager::load(ll::mod::Manifest manifest) {
212208
LLSECallEventsOnHotLoad(scriptEngine);
213209
}
214210
ExitEngineScope exit;
215-
plugin->onLoad([](ll::mod::Mod&) { return true; });
211+
216212
plugin->onDisable([this](ll::mod::Mod& self) {
217213
if (ll::getGamingStatus() == ll::GamingStatus::Stopping) {
218214
unload(self.getName());
@@ -271,16 +267,14 @@ ll::Expected<> PluginManager::unload(std::string_view name) {
271267
EngineOwnData::clearEngineObjects(scriptEngine);
272268
}
273269
EngineManager::unregisterEngine(scriptEngine);
274-
275-
if (auto plugin = std::static_pointer_cast<Plugin>(getMod(name))) {
276-
plugin->onUnload();
277-
}
278-
279-
eraseMod(name);
280-
281270
#ifdef LSE_BACKEND_NODEJS
282271
NodeJsHelper::stopEngine(scriptEngine);
283272
#endif
273+
274+
if (auto res = std::static_pointer_cast<Plugin>(getMod(name))->onUnload(); !res) {
275+
return res;
276+
}
277+
eraseMod(name);
284278
return {};
285279
} catch (const Exception&) {
286280
return ll::makeStringError("Failed to unload plugin {0}: {1}"_tr(name, "Unknown script exception"));
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
#pragma once
22
#include "ll/api/Expected.h"
3-
#include "ll/api/base/Macro.h"
43
#include "ll/api/mod/Manifest.h"
54
#include "ll/api/mod/ModManager.h"
65

0 commit comments

Comments
 (0)