Skip to content

Commit

Permalink
refactor: add Singleton<T> for singleton pattern
Browse files Browse the repository at this point in the history
  • Loading branch information
wu-vincent committed Mar 3, 2024
1 parent fbd4a50 commit df774a5
Show file tree
Hide file tree
Showing 15 changed files with 201 additions and 52 deletions.
7 changes: 4 additions & 3 deletions include/bedrock/command/command_registry.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<Command> createCommand(
const CommandRegistry::ParseToken &parse_token, const CommandOrigin &origin, int version,
std::string &error_message, std::vector<std::string> &error_params) const;
[[nodiscard]] BEDROCK_API std::unique_ptr<Command> createCommand(const CommandRegistry::ParseToken &parse_token,
const CommandOrigin &origin, int version,
std::string &error_message,
std::vector<std::string> &error_params) const;
[[nodiscard]] BEDROCK_API std::string describe(CommandParameterData const &) const;
BEDROCK_API void registerOverloadInternal(CommandRegistry::Signature &signature,
CommandRegistry::Overload &overload);
Expand Down
10 changes: 6 additions & 4 deletions include/endstone/command/command_map.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,19 @@
#include <string>
#include <vector>

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.
*
Expand All @@ -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
Expand Down
23 changes: 22 additions & 1 deletion include/endstone/detail/command/command_map.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,27 @@

#pragma once

#include <mutex>
#include <unordered_map>

#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> command) override;
void clearCommands() override;
[[nodiscard]] Command *getCommand(std::string name) const override;

private:
void setDefaultCommands();

Server &server_;
std::mutex mutex_;
std::unordered_map<std::string, std::shared_ptr<Command>> known_commands_;
};

} // namespace endstone::detail
26 changes: 26 additions & 0 deletions include/endstone/detail/command/defaults/version_command.h
Original file line number Diff line number Diff line change
@@ -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<std::string, std::string> &args) const override;
};

} // namespace endstone::detail
3 changes: 1 addition & 2 deletions include/endstone/detail/plugin/python_plugin_loader.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ namespace endstone::detail {
class PythonPluginLoader : public PluginLoader {
public:
explicit PythonPluginLoader(Server &server);
~PythonPluginLoader() override;

[[nodiscard]] std::vector<Plugin *> loadPlugins(const std::string &directory) override;
void enablePlugin(Plugin &plugin) const override;
Expand All @@ -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
10 changes: 3 additions & 7 deletions include/endstone/detail/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <string>
#include <string_view>

#include "bedrock/server/dedicated_server.h"
#include "endstone/detail/plugin/plugin_manager.h"
#include "endstone/plugin/plugin_manager.h"
#include "endstone/server.h"
Expand All @@ -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;
Expand All @@ -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<EndstonePluginManager> plugin_manager_;
};
Expand Down
44 changes: 44 additions & 0 deletions include/endstone/detail/singleton.h
Original file line number Diff line number Diff line change
@@ -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 <memory>
#include <stdexcept>

namespace endstone::detail {

template <typename T>
class Singleton {

public:
Singleton() = delete;

static void setInstance(std::unique_ptr<T> 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<T> mInstance = nullptr;
};
} // namespace endstone::detail
55 changes: 55 additions & 0 deletions src/endstone_core/command/command_map.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,58 @@
// limitations under the License.

#include "endstone/detail/command/command_map.h"

#include <algorithm>

#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<VersionCommand>());
}

} // namespace endstone::detail

bool endstone::detail::EndstoneCommandMap::registerCommand(std::shared_ptr<endstone::Command> 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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,32 +12,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.

#include <gtest/gtest.h>
#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<std::string, std::string> &args) const
{
EXPECT_NO_THROW(detail::EndstoneServer::getInstance());
EXPECT_EQ(&detail::EndstoneServer::getInstance(), &server_);
sender.sendMessage("This server is running Endstone version: {}",
Singleton<EndstoneServer>::getInstance().getVersion());
return true;
}

TEST_F(EndstoneServerTest, GetMinecraftVersion)
{
EXPECT_EQ(server_.getMinecraftVersion(), "1.2.3");
}

} // namespace endstone
3 changes: 2 additions & 1 deletion src/endstone_core/server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<EndstonePluginManager>(*this);
plugin_manager_->registerLoader(std::make_unique<CppPluginLoader>(*this));
Expand Down
10 changes: 9 additions & 1 deletion src/endstone_runtime/bedrock/dedicated_server.cpp
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
#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,
LevelSettings &level_settings,
AllowListFile &allow_list_file,
std::unique_ptr<PermissionsFile> &permissions_file)
{
auto &server = endstone::detail::EndstoneServer::getInstance();
Singleton<EndstoneServer>::setInstance(std::make_unique<EndstoneServer>(*this));
auto &server = Singleton<EndstoneServer>::getInstance();
server.getPluginManager().registerLoader(std::make_unique<PythonPluginLoader>(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);
Expand Down
10 changes: 7 additions & 3 deletions src/endstone_runtime/bedrock/server_instance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<EndstoneServer>::getInstance().loadPlugins();
ENDSTONE_HOOK_CALL_ORIGINAL(&ServerInstance::startServerThread, this);
}

void ServerInstanceEventCoordinator::sendServerThreadStarted(ServerInstance &instance)
{
endstone::detail::EndstoneServer::getInstance().enablePlugins();
Singleton<EndstoneServer>::getInstance().enablePlugins();
ENDSTONE_HOOK_CALL_ORIGINAL(&ServerInstanceEventCoordinator::sendServerThreadStarted, this, instance);
}

void ServerInstanceEventCoordinator::sendServerThreadStopped(ServerInstance &instance)
{
endstone::detail::EndstoneServer::getInstance().disablePlugins();
Singleton<EndstoneServer>::getInstance().disablePlugins();
ENDSTONE_HOOK_CALL_ORIGINAL(&ServerInstanceEventCoordinator::sendServerThreadStopped, this, instance);
}
12 changes: 6 additions & 6 deletions src/endstone_runtime/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,25 @@

#include <exception>

#include <pybind11/embed.h>
#include <spdlog/spdlog.h>

#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))
#else
#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<endstone::detail::PythonPluginLoader>(server));

endstone::detail::LoggerFactory::getLogger("EndstoneServer").info("Initialising...");
endstone::detail::hook::install();
return 0;
}
Expand Down
Loading

0 comments on commit df774a5

Please sign in to comment.