diff --git a/include/bedrock/command/command_registry.h b/include/bedrock/command/command_registry.h index 5459a70af..2b4519b25 100644 --- a/include/bedrock/command/command_registry.h +++ b/include/bedrock/command/command_registry.h @@ -124,9 +124,10 @@ class CommandRegistry { private: [[nodiscard]] BEDROCK_API const CommandRegistry::Signature *findCommand(const std::string &name) const; - [[nodiscard]] BEDROCK_API std::unique_ptr createCommand( - const CommandRegistry::ParseToken &parse_token, const CommandOrigin &origin, int version, - std::string &error_message, std::vector &error_params) const; + [[nodiscard]] BEDROCK_API std::unique_ptr createCommand(const CommandRegistry::ParseToken &parse_token, + const CommandOrigin &origin, int version, + std::string &error_message, + std::vector &error_params) const; [[nodiscard]] BEDROCK_API std::string describe(CommandParameterData const &) const; BEDROCK_API void registerOverloadInternal(CommandRegistry::Signature &signature, CommandRegistry::Overload &overload); diff --git a/include/endstone/command/command_map.h b/include/endstone/command/command_map.h index a2d5ee56f..a52677a10 100644 --- a/include/endstone/command/command_map.h +++ b/include/endstone/command/command_map.h @@ -18,17 +18,19 @@ #include #include -class Command; - namespace endstone { + +class Command; class CommandMap { public: - virtual ~CommandMap() = default; + CommandMap() = default; CommandMap(const CommandMap &) = delete; CommandMap &operator=(const CommandMap &) = delete; CommandMap(CommandMap &&) = default; CommandMap &operator=(CommandMap &&) = default; + virtual ~CommandMap() = default; + /** * Registers a command. * @@ -42,7 +44,7 @@ class CommandMap { /** * Clears all registered commands. */ - [[maybe_unused]] virtual void clearCommands() = 0; + virtual void clearCommands() = 0; /** * Gets the command registered to the specified name diff --git a/include/endstone/detail/command/command_map.h b/include/endstone/detail/command/command_map.h index 1e9a3b078..3e00f53f9 100644 --- a/include/endstone/detail/command/command_map.h +++ b/include/endstone/detail/command/command_map.h @@ -14,6 +14,27 @@ #pragma once +#include +#include + +#include "endstone/command/command.h" +#include "endstone/command/command_map.h" + namespace endstone::detail { -class EndstoneCommandMap {}; + +class EndstoneCommandMap : public CommandMap { +public: + explicit EndstoneCommandMap(Server &server); + bool registerCommand(std::shared_ptr command) override; + void clearCommands() override; + [[nodiscard]] Command *getCommand(std::string name) const override; + +private: + void setDefaultCommands(); + + Server &server_; + std::mutex mutex_; + std::unordered_map> known_commands_; +}; + } // namespace endstone::detail diff --git a/include/endstone/detail/command/defaults/version_command.h b/include/endstone/detail/command/defaults/version_command.h new file mode 100644 index 000000000..e7ee2a9be --- /dev/null +++ b/include/endstone/detail/command/defaults/version_command.h @@ -0,0 +1,26 @@ +// Copyright (c) 2024, The Endstone Project. (https://endstone.dev) All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "endstone/command/command.h" + +namespace endstone::detail { +class VersionCommand : public Command { +public: + VersionCommand(); + bool execute(CommandSender &sender, const std::map &args) const override; +}; + +} // namespace endstone::detail diff --git a/include/endstone/detail/plugin/python_plugin_loader.h b/include/endstone/detail/plugin/python_plugin_loader.h index 003d44869..41e158a9a 100644 --- a/include/endstone/detail/plugin/python_plugin_loader.h +++ b/include/endstone/detail/plugin/python_plugin_loader.h @@ -25,6 +25,7 @@ namespace endstone::detail { class PythonPluginLoader : public PluginLoader { public: explicit PythonPluginLoader(Server &server); + ~PythonPluginLoader() override; [[nodiscard]] std::vector loadPlugins(const std::string &directory) override; void enablePlugin(Plugin &plugin) const override; @@ -33,9 +34,7 @@ class PythonPluginLoader : public PluginLoader { private: [[nodiscard]] PluginLoader *pimpl() const; - pybind11::scoped_interpreter interpreter_; pybind11::object obj_; - pybind11::gil_scoped_release release_; }; } // namespace endstone::detail diff --git a/include/endstone/detail/server.h b/include/endstone/detail/server.h index d4676233e..0fe7fc65c 100644 --- a/include/endstone/detail/server.h +++ b/include/endstone/detail/server.h @@ -18,6 +18,7 @@ #include #include +#include "bedrock/server/dedicated_server.h" #include "endstone/detail/plugin/plugin_manager.h" #include "endstone/plugin/plugin_manager.h" #include "endstone/server.h" @@ -26,12 +27,7 @@ namespace endstone::detail { class EndstoneServer : public Server { public: - static EndstoneServer &getInstance() - { - static EndstoneServer instance; - return instance; - } - + explicit EndstoneServer(DedicatedServer &dedicated_server); EndstoneServer(EndstoneServer const &) = delete; EndstoneServer(EndstoneServer &&) = delete; EndstoneServer &operator=(EndstoneServer const &) = delete; @@ -49,9 +45,9 @@ class EndstoneServer : public Server { [[nodiscard]] std::string getMinecraftVersion() const override; private: - EndstoneServer(); void enablePlugin(Plugin &plugin) const; + DedicatedServer &dedicated_server_; Logger &logger_; std::unique_ptr plugin_manager_; }; diff --git a/include/endstone/detail/singleton.h b/include/endstone/detail/singleton.h new file mode 100644 index 000000000..ed65943e5 --- /dev/null +++ b/include/endstone/detail/singleton.h @@ -0,0 +1,44 @@ +// Copyright (c) 2024, The Endstone Project. (https://endstone.dev) All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include + +namespace endstone::detail { + +template +class Singleton { + +public: + Singleton() = delete; + + static void setInstance(std::unique_ptr instance) + { + if (mInstance) { + throw std::runtime_error("Instance is already set"); + } + mInstance = std::move(instance); + } + + static T &getInstance() + { + return *mInstance.get(); + } + +private: + inline static std::unique_ptr mInstance = nullptr; +}; +} // namespace endstone::detail diff --git a/src/endstone_core/command/command_map.cpp b/src/endstone_core/command/command_map.cpp index b45f81aff..9c5b42b50 100644 --- a/src/endstone_core/command/command_map.cpp +++ b/src/endstone_core/command/command_map.cpp @@ -13,3 +13,58 @@ // limitations under the License. #include "endstone/detail/command/command_map.h" + +#include + +#include "bedrock/command/command_registry.h" +#include "endstone/detail/command/defaults/version_command.h" + +namespace endstone::detail { + +EndstoneCommandMap::EndstoneCommandMap(Server &server) : server_(server) {} + +void EndstoneCommandMap::clearCommands() +{ + std::lock_guard lock(mutex_); + for (const auto &item : known_commands_) { + item.second->unregisterFrom(*this); + } + known_commands_.clear(); + setDefaultCommands(); +} + +Command *EndstoneCommandMap::getCommand(std::string name) const +{ + std::transform(name.begin(), name.end(), name.begin(), [](unsigned char c) { return std::tolower(c); }); + + auto it = known_commands_.find(name); + if (it == known_commands_.end()) { + return nullptr; + } + + return it->second.get(); +} + +void EndstoneCommandMap::setDefaultCommands() +{ + registerCommand(std::make_unique()); +} + +} // namespace endstone::detail + +bool endstone::detail::EndstoneCommandMap::registerCommand(std::shared_ptr command) +{ + if (!command) { + return false; + } + + auto name = command->getName(); + std::transform(name.begin(), name.end(), name.begin(), [](unsigned char c) { return std::tolower(c); }); + + // TODO: register to BDS + // registry.registerCommand(name, command->getDescription(), CommandPermissionLevel::Any, {128}, {0}); + // TODO: register the alias + // TODO: register the overloads from usage + + return false; +} diff --git a/tests/test_endstone_server.cpp b/src/endstone_core/command/defaults/version_command.cpp similarity index 54% rename from tests/test_endstone_server.cpp rename to src/endstone_core/command/defaults/version_command.cpp index 9a6c4e971..8d97d54d5 100644 --- a/tests/test_endstone_server.cpp +++ b/src/endstone_core/command/defaults/version_command.cpp @@ -12,32 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include "endstone/detail/command/defaults/version_command.h" -#include "bedrock/common.h" #include "endstone/detail/server.h" +#include "endstone/detail/singleton.h" -std::string Common::getGameVersionString() -{ - return "1.2.3"; -} - -namespace endstone { - -class EndstoneServerTest : public ::testing::Test { -protected: - detail::EndstoneServer &server_ = detail::EndstoneServer::getInstance(); -}; +endstone::detail::VersionCommand::VersionCommand() : Command("version", "Version command", {"/version"}) {} -TEST_F(EndstoneServerTest, GetInstance) +bool endstone::detail::VersionCommand::execute(CommandSender &sender, + const std::map &args) const { - EXPECT_NO_THROW(detail::EndstoneServer::getInstance()); - EXPECT_EQ(&detail::EndstoneServer::getInstance(), &server_); + sender.sendMessage("This server is running Endstone version: {}", + Singleton::getInstance().getVersion()); + return true; } - -TEST_F(EndstoneServerTest, GetMinecraftVersion) -{ - EXPECT_EQ(server_.getMinecraftVersion(), "1.2.3"); -} - -} // namespace endstone diff --git a/src/endstone_core/server.cpp b/src/endstone_core/server.cpp index 9f0081e14..61b39959b 100644 --- a/src/endstone_core/server.cpp +++ b/src/endstone_core/server.cpp @@ -28,7 +28,8 @@ namespace fs = std::filesystem; namespace endstone::detail { -EndstoneServer::EndstoneServer() : logger_(LoggerFactory::getLogger("EndstoneServer")) +EndstoneServer::EndstoneServer(DedicatedServer &dedicated_server) + : dedicated_server_(dedicated_server), logger_(LoggerFactory::getLogger("EndstoneServer")) { plugin_manager_ = std::make_unique(*this); plugin_manager_->registerLoader(std::make_unique(*this)); diff --git a/src/endstone_runtime/bedrock/dedicated_server.cpp b/src/endstone_runtime/bedrock/dedicated_server.cpp index ccb0f53b1..601a2791d 100644 --- a/src/endstone_runtime/bedrock/dedicated_server.cpp +++ b/src/endstone_runtime/bedrock/dedicated_server.cpp @@ -1,7 +1,13 @@ #include "bedrock/server/dedicated_server.h" #include "endstone/detail/hook.h" +#include "endstone/detail/plugin/python_plugin_loader.h" #include "endstone/detail/server.h" +#include "endstone/detail/singleton.h" + +using endstone::detail::EndstoneServer; +using endstone::detail::PythonPluginLoader; +using endstone::detail::Singleton; DedicatedServer::StartResult DedicatedServer::runDedicatedServerLoop(Core::FilePathManager &file_path_manager, PropertiesSettings &properties_settings, @@ -9,7 +15,9 @@ DedicatedServer::StartResult DedicatedServer::runDedicatedServerLoop(Core::FileP AllowListFile &allow_list_file, std::unique_ptr &permissions_file) { - auto &server = endstone::detail::EndstoneServer::getInstance(); + Singleton::setInstance(std::make_unique(*this)); + auto &server = Singleton::getInstance(); + server.getPluginManager().registerLoader(std::make_unique(server)); server.getLogger().info("Version: {} (Minecraft: {})", server.getVersion(), server.getMinecraftVersion()); return ENDSTONE_HOOK_CALL_ORIGINAL(&DedicatedServer::runDedicatedServerLoop, this, file_path_manager, properties_settings, level_settings, allow_list_file, permissions_file); diff --git a/src/endstone_runtime/bedrock/server_instance.cpp b/src/endstone_runtime/bedrock/server_instance.cpp index 545ce06af..b20cc7b06 100644 --- a/src/endstone_runtime/bedrock/server_instance.cpp +++ b/src/endstone_runtime/bedrock/server_instance.cpp @@ -16,21 +16,25 @@ #include "endstone/detail/hook.h" #include "endstone/detail/server.h" +#include "endstone/detail/singleton.h" + +using endstone::detail::EndstoneServer; +using endstone::detail::Singleton; void ServerInstance::startServerThread() { - endstone::detail::EndstoneServer::getInstance().loadPlugins(); + Singleton::getInstance().loadPlugins(); ENDSTONE_HOOK_CALL_ORIGINAL(&ServerInstance::startServerThread, this); } void ServerInstanceEventCoordinator::sendServerThreadStarted(ServerInstance &instance) { - endstone::detail::EndstoneServer::getInstance().enablePlugins(); + Singleton::getInstance().enablePlugins(); ENDSTONE_HOOK_CALL_ORIGINAL(&ServerInstanceEventCoordinator::sendServerThreadStarted, this, instance); } void ServerInstanceEventCoordinator::sendServerThreadStopped(ServerInstance &instance) { - endstone::detail::EndstoneServer::getInstance().disablePlugins(); + Singleton::getInstance().disablePlugins(); ENDSTONE_HOOK_CALL_ORIGINAL(&ServerInstanceEventCoordinator::sendServerThreadStopped, this, instance); } diff --git a/src/endstone_runtime/main.cpp b/src/endstone_runtime/main.cpp index c4c6d5f9c..e04e9a704 100644 --- a/src/endstone_runtime/main.cpp +++ b/src/endstone_runtime/main.cpp @@ -14,11 +14,11 @@ #include +#include #include #include "endstone/detail/hook.h" -#include "endstone/detail/plugin/python_plugin_loader.h" -#include "endstone/detail/server.h" +#include "endstone/detail/logger_factory.h" #if __GNUC__ #define ENDSTONE_RUNTIME_CTOR __attribute__((constructor)) @@ -26,13 +26,13 @@ #define ENDSTONE_RUNTIME_CTOR #endif +[[maybe_unused]] pybind11::scoped_interpreter gInterpreter; +[[maybe_unused]] pybind11::gil_scoped_release gRelease; + ENDSTONE_RUNTIME_CTOR int main() { try { - auto &server = endstone::detail::EndstoneServer::getInstance(); - server.getLogger().info("Initialising..."); - server.getPluginManager().registerLoader(std::make_unique(server)); - + endstone::detail::LoggerFactory::getLogger("EndstoneServer").info("Initialising..."); endstone::detail::hook::install(); return 0; } diff --git a/src/endstone_runtime/plugin/python_plugin_loader.cpp b/src/endstone_runtime/plugin/python_plugin_loader.cpp index 5fbd4335a..58f1c45ba 100644 --- a/src/endstone_runtime/plugin/python_plugin_loader.cpp +++ b/src/endstone_runtime/plugin/python_plugin_loader.cpp @@ -35,6 +35,12 @@ PythonPluginLoader::PythonPluginLoader(Server &server) : PluginLoader(server) } } +PythonPluginLoader::~PythonPluginLoader() +{ + py::gil_scoped_acquire gil{}; + obj_.dec_ref(); +} + std::vector PythonPluginLoader::loadPlugins(const std::string &directory) { auto plugins = pimpl()->loadPlugins(directory); diff --git a/test_package/src/test_server.cpp b/test_package/src/test_server.cpp index 0d47c1520..1b35444e8 100644 --- a/test_package/src/test_server.cpp +++ b/test_package/src/test_server.cpp @@ -74,7 +74,8 @@ void testPluginDisabling(EndstoneServer &server) int main() { - auto &server = EndstoneServer::getInstance(); + DedicatedServer dummy_ds; + EndstoneServer server(dummy_ds); testLogger(server); testPluginLoading(server); testPluginEnabling(server);