diff --git a/Server/Components/LegacyConfig/config_main.cpp b/Server/Components/LegacyConfig/config_main.cpp index 5045a29b5..527f28270 100644 --- a/Server/Components/LegacyConfig/config_main.cpp +++ b/Server/Components/LegacyConfig/config_main.cpp @@ -75,6 +75,8 @@ const FlatHashMap types = { { "stream_distance", ParamType::Float }, { "stream_rate", ParamType::Int }, { "maxnpc", ParamType::Int }, + { "max_global_textdraws", ParamType::Int }, + { "max_player_textdraws", ParamType::Int }, { "lagcompmode", ParamType::Int }, { "useartwork", ParamType::Bool }, { "artpath", ParamType::String }, @@ -122,6 +124,8 @@ const FlatHashMap dictionary = { { "stream_distance", "network.stream_radius" }, { "stream_rate", "network.stream_rate" }, { "maxnpc", "max_bots" }, + { "max_global_textdraws", "textdraw.global_limit" }, + { "max_player_textdraws", "textdraw.player_limit" }, { "lagcompmode", "game.lag_compensation_mode" }, { "useartwork", "artwork.enable" }, { "artpath", "artwork.models_path" } diff --git a/Server/Components/TextDraws/textdraw.hpp b/Server/Components/TextDraws/textdraw.hpp index 2f952a6c8..a2d36cf8d 100644 --- a/Server/Components/TextDraws/textdraw.hpp +++ b/Server/Components/TextDraws/textdraw.hpp @@ -35,13 +35,15 @@ class TextDrawBase : public T, public PoolIDProvider, public NoCopy Vector3 previewRotation = Vector3(0.f); Pair previewVehicleColours = std::make_pair(-1, -1); float previewZoom = 1.f; + int globalTextDrawPoolSize; public: - TextDrawBase(Vector2 pos, StringView text, TextDrawStyle style = TextDrawStyle_FontAharoniBold, int previewModel = 0) + TextDrawBase(int globalTextDrawPoolSize, Vector2 pos, StringView text, TextDrawStyle style = TextDrawStyle_FontAharoniBold, int previewModel = 0) : pos(pos) , text(text) , style(style) , previewModel(previewModel) + , globalTextDrawPoolSize(globalTextDrawPoolSize) { trimText(); } @@ -260,6 +262,7 @@ class TextDrawBase : public T, public PoolIDProvider, public NoCopy { NetCode::RPC::PlayerShowTextDraw playerShowTextDrawRPC; playerShowTextDrawRPC.PlayerTextDraw = isPlayerTextDraw; + playerShowTextDrawRPC.GlobalTextDrawPoolSize = globalTextDrawPoolSize; playerShowTextDrawRPC.UseBox = box; switch (alignment) { @@ -301,6 +304,7 @@ class TextDrawBase : public T, public PoolIDProvider, public NoCopy { NetCode::RPC::PlayerHideTextDraw playerHideTextDrawRPC; playerHideTextDrawRPC.PlayerTextDraw = isPlayerTextDraw; + playerHideTextDrawRPC.GlobalTextDrawPoolSize = globalTextDrawPoolSize; playerHideTextDrawRPC.TextDrawID = poolID; PacketHelper::send(playerHideTextDrawRPC, player); } @@ -309,6 +313,7 @@ class TextDrawBase : public T, public PoolIDProvider, public NoCopy { NetCode::RPC::PlayerTextDrawSetString playerTextDrawSetStringRPC; playerTextDrawSetStringRPC.PlayerTextDraw = isPlayerTextDraw; + playerTextDrawSetStringRPC.GlobalTextDrawPoolSize = globalTextDrawPoolSize; playerTextDrawSetStringRPC.TextDrawID = poolID; playerTextDrawSetStringRPC.Text = txt; PacketHelper::send(playerTextDrawSetStringRPC, player); @@ -338,9 +343,12 @@ class TextDraw final : public TextDrawBase private: UniqueIDArray shownFor_; - using TextDrawBase::TextDrawBase; - public: + TextDraw(int globalTextDrawPoolSize, Vector2 pos, StringView text, TextDrawStyle style = TextDrawStyle_FontAharoniBold, int previewModel = 0) + : TextDrawBase(globalTextDrawPoolSize, pos, text, style, previewModel) + { + } + void removeFor(int pid, IPlayer& player) { if (shownFor_.valid(pid)) @@ -408,8 +416,8 @@ class PlayerTextDraw final : public TextDrawBase bool shown = false; public: - PlayerTextDraw(IPlayer& player, Vector2 pos, StringView text, TextDrawStyle style = TextDrawStyle_FontAharoniBold, int previewModel = 0) - : TextDrawBase(pos, text, style, previewModel) + PlayerTextDraw(int globalTextDrawPoolSize, IPlayer& player, Vector2 pos, StringView text, TextDrawStyle style = TextDrawStyle_FontAharoniBold, int previewModel = 0) + : TextDrawBase(globalTextDrawPoolSize, pos, text, style, previewModel) , player(player) { } diff --git a/Server/Components/TextDraws/textdraws_main.cpp b/Server/Components/TextDraws/textdraws_main.cpp index 3a5413224..9fec5a283 100644 --- a/Server/Components/TextDraws/textdraws_main.cpp +++ b/Server/Components/TextDraws/textdraws_main.cpp @@ -9,14 +9,258 @@ #include "textdraw.hpp" #include #include +#include +#include +#include +#include +#include +#include using namespace Impl; +namespace +{ +template +class RuntimeMarkedPoolStorage final : public NoCopy +{ +public: + using Iterator = MarkedPoolIterator>; + static constexpr size_t Lower = 0; + + explicit RuntimeMarkedPoolStorage(size_t capacity) + { + resize(capacity); + } + + ~RuntimeMarkedPoolStorage() + { + clear(); + } + + void resize(size_t capacity) + { + assert(entries_.empty()); + pool_.assign(capacity, nullptr); + refs_.assign(capacity, RefCountType(0)); + deleted_.assign(capacity, false); + lowestFreeIndex_ = Lower; + } + + size_t upper() const + { + return pool_.size(); + } + + Pair bounds() const + { + return std::make_pair(Lower, upper()); + } + + template + Type* emplace(Args&&... args) + { + const int freeIdx = findFreeIndex(); + if (freeIdx < 0) + { + return nullptr; + } + + const int id = claimHint(freeIdx, std::forward(args)...); + return id < 0 ? nullptr : get(id); + } + + Type* get(int index) + { + return valid(index) ? pool_[static_cast(index)] : nullptr; + } + + const Type* get(int index) const + { + return valid(index) ? pool_[static_cast(index)] : nullptr; + } + + void release(int index, bool force) + { + (void)force; + if (!inBounds(index)) + { + return; + } + + if (refs_[static_cast(index)] > 0) + { + deleted_[static_cast(index)] = true; + } + else + { + deleted_[static_cast(index)] = false; + remove(index); + } + } + + void lock(int index) + { + if (!inBounds(index)) + { + return; + } + ++refs_[static_cast(index)]; + assert(refs_[static_cast(index)] < std::numeric_limits::max()); + } + + bool unlock(int index) + { + if (!inBounds(index) || refs_[static_cast(index)] == 0) + { + return false; + } + + const size_t internalIndex = static_cast(index); + if (--refs_[internalIndex] == 0 && deleted_[internalIndex]) + { + remove(index); + return true; + } + return false; + } + + Iterator begin() + { + return Iterator(*this, entries_, entries_.begin()); + } + + Iterator end() + { + return Iterator(*this, entries_, entries_.end()); + } + + void clear() + { + for (Interface* const entry : entries_) + { + eventDispatcher_.dispatch(&PoolEventHandler::onPoolEntryDestroyed, *entry); + delete static_cast(entry); + } + entries_.clear(); + std::fill(pool_.begin(), pool_.end(), nullptr); + std::fill(refs_.begin(), refs_.end(), RefCountType(0)); + std::fill(deleted_.begin(), deleted_.end(), false); + lowestFreeIndex_ = Lower; + } + + const FlatPtrHashSet& _entries() const + { + return entries_; + } + + IEventDispatcher>& getEventDispatcher() + { + return eventDispatcher_; + } + +private: + bool inBounds(int index) const + { + return index >= static_cast(Lower) && static_cast(index) < pool_.size(); + } + + bool valid(int index) const + { + return inBounds(index) && pool_[static_cast(index)] != nullptr; + } + + int findFreeIndex() const + { + for (size_t index = static_cast(lowestFreeIndex_); index < pool_.size(); ++index) + { + if (pool_[index] == nullptr) + { + return static_cast(index); + } + } + return -1; + } + + template + int claim(Args&&... args) + { + const int freeIdx = findFreeIndex(); + if (freeIdx < 0) + { + return -1; + } + + if (freeIdx == lowestFreeIndex_) + { + ++lowestFreeIndex_; + } + claimAt(freeIdx, std::forward(args)...); + return freeIdx; + } + + template + int claimHint(int hint, Args&&... args) + { + if (inBounds(hint) && !valid(hint)) + { + if (hint == lowestFreeIndex_) + { + ++lowestFreeIndex_; + } + claimAt(hint, std::forward(args)...); + return hint; + } + return claim(std::forward(args)...); + } + + template + void claimAt(int index, Args&&... args) + { + const size_t internalIndex = static_cast(index); + pool_[internalIndex] = new Type(std::forward(args)...); + entries_.insert(pool_[internalIndex]); + if constexpr (std::is_base_of::value) + { + pool_[internalIndex]->poolID = index; + } + eventDispatcher_.dispatch(&PoolEventHandler::onPoolEntryCreated, *pool_[internalIndex]); + } + + void remove(int index) + { + if (!valid(index)) + { + return; + } + + const size_t internalIndex = static_cast(index); + if (index < lowestFreeIndex_) + { + lowestFreeIndex_ = index; + } + Type* entry = pool_[internalIndex]; + entries_.erase(entry); + eventDispatcher_.dispatch(&PoolEventHandler::onPoolEntryDestroyed, *entry); + delete entry; + pool_[internalIndex] = nullptr; + deleted_[internalIndex] = false; + } + + std::vector pool_; + std::vector refs_; + std::vector deleted_; + FlatPtrHashSet entries_; + int lowestFreeIndex_ = Lower; + DefaultEventDispatcher> eventDispatcher_; +}; +} + class PlayerTextDrawData final : public IPlayerTextDrawData { private: IPlayer& player; - MarkedPoolStorage storage; + int globalTextDrawPoolSize; + RuntimeMarkedPoolStorage storage; bool selecting; public: @@ -25,8 +269,10 @@ class PlayerTextDrawData final : public IPlayerTextDrawData selecting = false; } - PlayerTextDrawData(IPlayer& player) + PlayerTextDrawData(IPlayer& player, int playerTextDrawPoolSize, int globalTextDrawPoolSize) : player(player) + , globalTextDrawPoolSize(globalTextDrawPoolSize) + , storage(playerTextDrawPoolSize) , selecting(false) { } @@ -56,12 +302,12 @@ class PlayerTextDrawData final : public IPlayerTextDrawData IPlayerTextDraw* create(Vector2 position, StringView text) override { - return storage.emplace(player, position, text); + return storage.emplace(globalTextDrawPoolSize, player, position, text); } IPlayerTextDraw* create(Vector2 position, int model) override { - return storage.emplace(player, position, "_", TextDrawStyle_Preview, model); + return storage.emplace(globalTextDrawPoolSize, player, position, "_", TextDrawStyle_Preview, model); } void freeExtension() override @@ -77,7 +323,7 @@ class PlayerTextDrawData final : public IPlayerTextDrawData virtual Pair bounds() const override { - return std::make_pair(storage.Lower, storage.Upper); + return storage.bounds(); } IPlayerTextDraw* get(int index) override @@ -121,7 +367,9 @@ class TextDrawsComponent final : public ITextDrawsComponent, public PlayerConnec { private: ICore* core = nullptr; - MarkedPoolStorage storage; + int globalTextDrawPoolSize = GLOBAL_TEXTDRAW_POOL_SIZE; + int playerTextDrawPoolSize = PLAYER_TEXTDRAW_POOL_SIZE; + RuntimeMarkedPoolStorage storage; DefaultEventDispatcher dispatcher; public: @@ -146,6 +394,7 @@ class TextDrawsComponent final : public ITextDrawsComponent, public PlayerConnec bool onReceive(IPlayer& peer, NetworkBitStream& bs) override { NetCode::RPC::OnPlayerSelectTextDraw RPC; + RPC.GlobalTextDrawPoolSize = self.globalTextDrawPoolSize; if (!RPC.read(bs)) { return false; @@ -189,13 +438,38 @@ class TextDrawsComponent final : public ITextDrawsComponent, public PlayerConnec } playerSelectTextDrawEventHandler; TextDrawsComponent() - : playerSelectTextDrawEventHandler(*this) + : storage(GLOBAL_TEXTDRAW_POOL_SIZE) + , playerSelectTextDrawEventHandler(*this) { } void onLoad(ICore* c) override { core = c; + IConfig& config = core->getConfig(); + const int* configuredGlobalLimit = config.getInt("textdraw.global_limit"); + const int* configuredPlayerLimit = config.getInt("textdraw.player_limit"); + const int globalLimit = configuredGlobalLimit ? *configuredGlobalLimit : GLOBAL_TEXTDRAW_POOL_SIZE; + const int playerLimit = configuredPlayerLimit ? *configuredPlayerLimit : PLAYER_TEXTDRAW_POOL_SIZE; + const long long totalLimit = static_cast(globalLimit) + static_cast(playerLimit); + if (globalLimit < 0 || playerLimit < 0 || totalLimit > INVALID_TEXTDRAW) + { + core->logLn(LogLevel::Warning, + "Textdraw limits exceed the maximum allowed value, using defaults."); + } + else + { + globalTextDrawPoolSize = globalLimit; + playerTextDrawPoolSize = playerLimit; + } + if (globalTextDrawPoolSize != GLOBAL_TEXTDRAW_POOL_SIZE || playerTextDrawPoolSize != PLAYER_TEXTDRAW_POOL_SIZE) + { + core->logLn(LogLevel::Message, + "Using custom textdraw limits: global %d, player %d.", + globalTextDrawPoolSize, + playerTextDrawPoolSize); + } + storage.resize(globalTextDrawPoolSize); core->getPlayers().getPlayerConnectDispatcher().addEventHandler(this); core->getPlayers().getPoolEventDispatcher().addEventHandler(this); NetCode::RPC::OnPlayerSelectTextDraw::addEventHandler(*core, &playerSelectTextDrawEventHandler); @@ -219,7 +493,7 @@ class TextDrawsComponent final : public ITextDrawsComponent, public PlayerConnec void onPlayerConnect(IPlayer& player) override { - player.addExtension(new PlayerTextDrawData(player), true); + player.addExtension(new PlayerTextDrawData(player, playerTextDrawPoolSize, globalTextDrawPoolSize), true); } void onPoolEntryDestroyed(IPlayer& player) override @@ -240,12 +514,12 @@ class TextDrawsComponent final : public ITextDrawsComponent, public PlayerConnec ITextDraw* create(Vector2 position, StringView text) override { - return storage.emplace(position, text); + return storage.emplace(globalTextDrawPoolSize, position, text); } ITextDraw* create(Vector2 position, int model) override { - return storage.emplace(position, "_", TextDrawStyle_Preview, model); + return storage.emplace(globalTextDrawPoolSize, position, "_", TextDrawStyle_Preview, model); } void free() override @@ -255,7 +529,7 @@ class TextDrawsComponent final : public ITextDrawsComponent, public PlayerConnec virtual Pair bounds() const override { - return std::make_pair(storage.Lower, storage.Upper); + return storage.bounds(); } ITextDraw* get(int index) override diff --git a/Shared/NetCode/textdraw.hpp b/Shared/NetCode/textdraw.hpp index 92c520338..f98ec723f 100644 --- a/Shared/NetCode/textdraw.hpp +++ b/Shared/NetCode/textdraw.hpp @@ -11,6 +11,7 @@ #include #include #include +#include namespace NetCode { @@ -20,6 +21,7 @@ namespace RPC { bool PlayerTextDraw; int TextDrawID; + int GlobalTextDrawPoolSize = GLOBAL_TEXTDRAW_POOL_SIZE; bool UseBox; int Alignment; bool Proportional; @@ -48,7 +50,7 @@ namespace RPC void write(NetworkBitStream& bs) const { uint8_t flags = UseBox | (Alignment << 1) | (Proportional << 4); - bs.writeUINT16(PlayerTextDraw ? GLOBAL_TEXTDRAW_POOL_SIZE + TextDrawID : TextDrawID); + bs.writeUINT16(PlayerTextDraw ? GlobalTextDrawPoolSize + TextDrawID : TextDrawID); bs.writeUINT8(flags); bs.writeVEC2(LetterSize); bs.writeUINT32(LetterColour.ABGR()); @@ -73,6 +75,7 @@ namespace RPC { bool PlayerTextDraw; int TextDrawID; + int GlobalTextDrawPoolSize = GLOBAL_TEXTDRAW_POOL_SIZE; bool read(NetworkBitStream& bs) { @@ -81,7 +84,7 @@ namespace RPC void write(NetworkBitStream& bs) const { - bs.writeUINT16(PlayerTextDraw ? GLOBAL_TEXTDRAW_POOL_SIZE + TextDrawID : TextDrawID); + bs.writeUINT16(PlayerTextDraw ? GlobalTextDrawPoolSize + TextDrawID : TextDrawID); } }; @@ -89,6 +92,7 @@ namespace RPC { bool PlayerTextDraw; int TextDrawID; + int GlobalTextDrawPoolSize = GLOBAL_TEXTDRAW_POOL_SIZE; HybridString<256> Text; bool read(NetworkBitStream& bs) @@ -98,7 +102,7 @@ namespace RPC void write(NetworkBitStream& bs) const { - bs.writeUINT16(PlayerTextDraw ? GLOBAL_TEXTDRAW_POOL_SIZE + TextDrawID : TextDrawID); + bs.writeUINT16(PlayerTextDraw ? GlobalTextDrawPoolSize + TextDrawID : TextDrawID); bs.writeDynStr16(Text); } }; @@ -125,6 +129,7 @@ namespace RPC bool PlayerTextDraw; bool Invalid; int TextDrawID; + int GlobalTextDrawPoolSize = GLOBAL_TEXTDRAW_POOL_SIZE; bool read(NetworkBitStream& bs) { @@ -132,10 +137,10 @@ namespace RPC Invalid = TextDrawID == INVALID_TEXTDRAW; if (!Invalid) { - PlayerTextDraw = TextDrawID >= GLOBAL_TEXTDRAW_POOL_SIZE; + PlayerTextDraw = TextDrawID >= GlobalTextDrawPoolSize; if (PlayerTextDraw) { - TextDrawID -= GLOBAL_TEXTDRAW_POOL_SIZE; + TextDrawID -= GlobalTextDrawPoolSize; } } return res;