codex-5.2: синхронизация ресурсов модов, частичная перезагрузка модов
This commit is contained in:
@@ -221,11 +221,18 @@ std::tuple<ResourceId, std::optional<AssetsManager::DataEntry>&> AssetsManager::
|
||||
|
||||
for(size_t index = 0; index < table.size(); index++) {
|
||||
auto& entry = *table[index];
|
||||
if(index == 0 && entry.Empty.test(0)) {
|
||||
entry.Empty.reset(0);
|
||||
}
|
||||
|
||||
if(entry.IsFull)
|
||||
continue;
|
||||
|
||||
uint32_t pos = entry.Empty._Find_first();
|
||||
if(pos == entry.Empty.size()) {
|
||||
entry.IsFull = true;
|
||||
continue;
|
||||
}
|
||||
entry.Empty.reset(pos);
|
||||
|
||||
if(entry.Empty._Find_next(pos) == entry.Empty.size())
|
||||
@@ -233,13 +240,23 @@ std::tuple<ResourceId, std::optional<AssetsManager::DataEntry>&> AssetsManager::
|
||||
|
||||
id = index*TableEntry<DataEntry>::ChunkSize + pos;
|
||||
data = &entry.Entries[pos];
|
||||
break;
|
||||
}
|
||||
|
||||
if(!data) {
|
||||
table.emplace_back(std::make_unique<TableEntry<DataEntry>>());
|
||||
id = (table.size()-1)*TableEntry<DataEntry>::ChunkSize;
|
||||
data = &table.back()->Entries[0];
|
||||
table.back()->Empty.reset(0);
|
||||
auto& entry = *table.back();
|
||||
if(table.size() == 1 && entry.Empty.test(0)) {
|
||||
entry.Empty.reset(0);
|
||||
}
|
||||
|
||||
uint32_t pos = entry.Empty._Find_first();
|
||||
entry.Empty.reset(pos);
|
||||
if(entry.Empty._Find_next(pos) == entry.Empty.size())
|
||||
entry.IsFull = true;
|
||||
|
||||
id = (table.size()-1)*TableEntry<DataEntry>::ChunkSize + pos;
|
||||
data = &entry.Entries[pos];
|
||||
|
||||
// Расширяем таблицу с ресурсами, если необходимо
|
||||
if(type == EnumAssets::Nodestate)
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace LV::Server {
|
||||
ContentManager::ContentManager(AssetsManager &am)
|
||||
: AM(am)
|
||||
{
|
||||
|
||||
std::fill(std::begin(NextId), std::end(NextId), 1);
|
||||
}
|
||||
|
||||
ContentManager::~ContentManager() = default;
|
||||
@@ -111,6 +111,31 @@ void ContentManager::unRegisterModifier(EnumDefContent type, const std::string&
|
||||
ProfileChanges[(int) type].push_back(id);
|
||||
}
|
||||
|
||||
void ContentManager::markAllProfilesDirty(EnumDefContent type) {
|
||||
const auto &table = ContentKeyToId[(int) type];
|
||||
for(const auto& domainPair : table) {
|
||||
for(const auto& keyPair : domainPair.second) {
|
||||
ProfileChanges[(int) type].push_back(keyPair.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ResourceId> ContentManager::collectProfileIds(EnumDefContent type) const {
|
||||
std::vector<ResourceId> ids;
|
||||
const auto &table = ContentKeyToId[(int) type];
|
||||
|
||||
for(const auto& domainPair : table) {
|
||||
for(const auto& keyPair : domainPair.second) {
|
||||
ids.push_back(keyPair.second);
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(ids.begin(), ids.end());
|
||||
auto last = std::unique(ids.begin(), ids.end());
|
||||
ids.erase(last, ids.end());
|
||||
return ids;
|
||||
}
|
||||
|
||||
ContentManager::Out_buildEndProfiles ContentManager::buildEndProfiles() {
|
||||
Out_buildEndProfiles result;
|
||||
|
||||
@@ -138,4 +163,4 @@ ContentManager::Out_buildEndProfiles ContentManager::buildEndProfiles() {
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ class ContentManager {
|
||||
|
||||
|
||||
// Следующие идентификаторы регистрации контента
|
||||
ResourceId NextId[(int) EnumDefContent::MAX_ENUM] = {0};
|
||||
ResourceId NextId[(int) EnumDefContent::MAX_ENUM] = {};
|
||||
// Домен -> {ключ -> идентификатор}
|
||||
std::unordered_map<std::string, std::unordered_map<std::string, ResourceId>> ContentKeyToId[(int) EnumDefContent::MAX_ENUM];
|
||||
|
||||
@@ -143,6 +143,10 @@ public:
|
||||
// Регистрация модификатора предмета модом
|
||||
void registerModifier(EnumDefContent type, const std::string& mod, const std::string& domain, const std::string& key, const sol::table& profile);
|
||||
void unRegisterModifier(EnumDefContent type, const std::string& mod, const std::string& domain, const std::string& key);
|
||||
// Пометить все профили типа как изменённые (например, после перезагрузки ассетов)
|
||||
void markAllProfilesDirty(EnumDefContent type);
|
||||
// Список всех зарегистрированных профилей выбранного типа
|
||||
std::vector<ResourceId> collectProfileIds(EnumDefContent type) const;
|
||||
// Компилирует изменённые профили
|
||||
struct Out_buildEndProfiles {
|
||||
std::vector<ResourceId> ChangedProfiles[(int) EnumDefContent::MAX_ENUM];
|
||||
@@ -209,4 +213,4 @@ private:
|
||||
AssetsManager& AM;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <sol/forward.hpp>
|
||||
#include <sol/protected_function_result.hpp>
|
||||
#include <sstream>
|
||||
@@ -1107,7 +1108,7 @@ coro<> GameServer::pushSocketGameProtocol(tcp::socket socket, const std::string
|
||||
co_await Net::AsyncSocket::write<uint8_t>(socket, 0);
|
||||
|
||||
External.NewConnectedPlayers.lock_write()
|
||||
->push_back(std::make_shared<RemoteClient>(IOC, std::move(socket), username));
|
||||
->push_back(std::make_shared<RemoteClient>(IOC, std::move(socket), username, this));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1444,7 +1445,7 @@ void GameServer::init(fs::path worldPath) {
|
||||
|
||||
{
|
||||
sol::table t = LuaMainState.create_table();
|
||||
Content.CM.registerBase(EnumDefContent::Node, "core", "none", t);
|
||||
// Content.CM.registerBase(EnumDefContent::Node, "core", "none", t);
|
||||
Content.CM.registerBase(EnumDefContent::World, "test", "devel_world", t);
|
||||
}
|
||||
|
||||
@@ -1453,11 +1454,11 @@ void GameServer::init(fs::path worldPath) {
|
||||
|
||||
// TODO: регистрация контента из mod/content/*
|
||||
|
||||
Content.CM.buildEndProfiles();
|
||||
|
||||
pushEvent("preInit");
|
||||
pushEvent("highPreInit");
|
||||
|
||||
Content.CM.buildEndProfiles();
|
||||
|
||||
|
||||
LOG.info() << "Инициализация";
|
||||
initLua();
|
||||
@@ -1674,6 +1675,13 @@ void GameServer::initLuaPost() {
|
||||
|
||||
}
|
||||
|
||||
void GameServer::requestModsReload() {
|
||||
bool expected = false;
|
||||
if(ModsReloadRequested.compare_exchange_strong(expected, true)) {
|
||||
LOG.info() << "Запрошена перезагрузка модов";
|
||||
}
|
||||
}
|
||||
|
||||
void GameServer::stepConnections() {
|
||||
// Подключить новых игроков
|
||||
if(!External.NewConnectedPlayers.no_lock_readable().empty()) {
|
||||
@@ -1715,9 +1723,42 @@ void GameServer::stepConnections() {
|
||||
}
|
||||
|
||||
void GameServer::stepModInitializations() {
|
||||
if(ModsReloadRequested.exchange(false)) {
|
||||
reloadMods();
|
||||
}
|
||||
BackingChunkPressure.endWithResults();
|
||||
}
|
||||
|
||||
void GameServer::reloadMods() {
|
||||
LOG.info() << "Перезагрузка модов: ассеты и зависимости";
|
||||
|
||||
AssetsManager::ResourceChangeObj changes = Content.AM.recheckResources(AssetsInit);
|
||||
AssetsManager::Out_applyResourceChange applied = Content.AM.applyResourceChange(changes);
|
||||
|
||||
size_t changedCount = 0;
|
||||
size_t lostCount = 0;
|
||||
for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) {
|
||||
for(const auto& entry : applied.NewOrChange[type]) {
|
||||
Content.OnContentChanges.AssetsInfo[type].push_back(entry.first);
|
||||
changedCount++;
|
||||
}
|
||||
|
||||
lostCount += applied.Lost[type].size();
|
||||
}
|
||||
|
||||
Content.CM.markAllProfilesDirty(EnumDefContent::Node);
|
||||
Content.CM.buildEndProfiles();
|
||||
|
||||
std::vector<ResourceId> nodeIds = Content.CM.collectProfileIds(EnumDefContent::Node);
|
||||
if(!nodeIds.empty()) {
|
||||
Content.OnContentChanges.Node.append_range(nodeIds);
|
||||
}
|
||||
|
||||
LOG.info() << "Перезагрузка завершена: обновлено ассетов=" << changedCount
|
||||
<< " удалено=" << lostCount
|
||||
<< " нод=" << nodeIds.size();
|
||||
}
|
||||
|
||||
IWorldSaveBackend::TickSyncInfo_Out GameServer::stepDatabaseSync() {
|
||||
IWorldSaveBackend::TickSyncInfo_In toDB;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <Common/Net.hpp>
|
||||
#include <Common/Lockable.hpp>
|
||||
#include <atomic>
|
||||
#include <boost/asio/any_io_executor.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <condition_variable>
|
||||
@@ -58,6 +59,7 @@ class GameServer : public AsyncObject {
|
||||
|
||||
bool IsAlive = true, IsGoingShutdown = false;
|
||||
std::string ShutdownReason;
|
||||
std::atomic<bool> ModsReloadRequested = false;
|
||||
static constexpr float
|
||||
PerTickDuration = 1/30.f, // Минимальная и стартовая длина такта
|
||||
PerTickAdjustment = 1/60.f; // Подгонка длительности такта в случае провисаний
|
||||
@@ -283,6 +285,7 @@ public:
|
||||
void waitShutdown() {
|
||||
UseLock.wait_no_use();
|
||||
}
|
||||
void requestModsReload();
|
||||
|
||||
// Подключение tcp сокета
|
||||
coro<> pushSocketConnect(tcp::socket socket);
|
||||
@@ -315,6 +318,7 @@ private:
|
||||
*/
|
||||
|
||||
void stepModInitializations();
|
||||
void reloadMods();
|
||||
|
||||
/*
|
||||
Пересчёт зон видимости игроков, если необходимо
|
||||
@@ -364,4 +368,4 @@ private:
|
||||
void stepSyncContent();
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
#include "Common/Abstract.hpp"
|
||||
#include "Common/Net.hpp"
|
||||
#include "Server/Abstract.hpp"
|
||||
#include "Server/GameServer.hpp"
|
||||
#include "Server/World.hpp"
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/system/system_error.hpp>
|
||||
#include <exception>
|
||||
@@ -13,6 +15,23 @@
|
||||
|
||||
namespace LV::Server {
|
||||
|
||||
namespace {
|
||||
|
||||
const char* assetTypeName(EnumAssets type) {
|
||||
switch(type) {
|
||||
case EnumAssets::Nodestate: return "nodestate";
|
||||
case EnumAssets::Model: return "model";
|
||||
case EnumAssets::Texture: return "texture";
|
||||
case EnumAssets::Particle: return "particle";
|
||||
case EnumAssets::Animation: return "animation";
|
||||
case EnumAssets::Sound: return "sound";
|
||||
case EnumAssets::Font: return "font";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RemoteClient::~RemoteClient() {
|
||||
shutdown(EnumDisconnect::ByInterface, "~RemoteClient()");
|
||||
if(Socket.isAlive()) {
|
||||
@@ -487,9 +506,13 @@ ResourceRequest RemoteClient::pushPreparedPackets() {
|
||||
nextRequest = std::move(lock->NextRequest);
|
||||
}
|
||||
|
||||
if(AssetsInWork.AssetsPacket.size()) {
|
||||
toSend.push_back(std::move(AssetsInWork.AssetsPacket));
|
||||
if(!AssetsInWork.AssetsPackets.empty()) {
|
||||
for(Net::Packet& packet : AssetsInWork.AssetsPackets)
|
||||
toSend.push_back(std::move(packet));
|
||||
AssetsInWork.AssetsPackets.clear();
|
||||
}
|
||||
if(AssetsInWork.AssetsPacket.size())
|
||||
toSend.push_back(std::move(AssetsInWork.AssetsPacket));
|
||||
|
||||
{
|
||||
Net::Packet p;
|
||||
@@ -508,6 +531,7 @@ ResourceRequest RemoteClient::pushPreparedPackets() {
|
||||
void RemoteClient::informateAssets(const std::vector<std::tuple<EnumAssets, ResourceId, const std::string, const std::string, Resource>>& resources)
|
||||
{
|
||||
std::vector<std::tuple<EnumAssets, ResourceId, const std::string, const std::string, Hash_t, size_t>> newForClient;
|
||||
static std::atomic<uint32_t> debugSendLogCount = 0;
|
||||
|
||||
for(auto& [type, resId, domain, key, resource] : resources) {
|
||||
auto hash = resource.hash();
|
||||
@@ -526,6 +550,22 @@ void RemoteClient::informateAssets(const std::vector<std::tuple<EnumAssets, Reso
|
||||
if(it == AssetsInWork.OnClient.end() || *it != hash) {
|
||||
AssetsInWork.OnClient.insert(it, hash);
|
||||
AssetsInWork.ToSend.emplace_back(type, domain, key, resId, resource, 0);
|
||||
if(domain == "test"
|
||||
&& (type == EnumAssets::Nodestate
|
||||
|| type == EnumAssets::Model
|
||||
|| type == EnumAssets::Texture))
|
||||
{
|
||||
if(debugSendLogCount.fetch_add(1) < 64) {
|
||||
LOG.debug() << "Queue resource send type=" << assetTypeName(type)
|
||||
<< " id=" << resId
|
||||
<< " key=" << domain << ':' << key
|
||||
<< " size=" << resource.size()
|
||||
<< " hash=" << int(hash[0]) << '.'
|
||||
<< int(hash[1]) << '.'
|
||||
<< int(hash[2]) << '.'
|
||||
<< int(hash[3]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOG.warn() << "Клиент повторно запросил имеющийся у него ресурс";
|
||||
}
|
||||
@@ -720,6 +760,7 @@ coro<> RemoteClient::rP_System(Net::AsyncSocket &sock) {
|
||||
}
|
||||
case ToServer::L2System::ResourceRequest:
|
||||
{
|
||||
static std::atomic<uint32_t> debugRequestLogCount = 0;
|
||||
uint16_t count = co_await sock.read<uint16_t>();
|
||||
std::vector<Hash_t> hashes;
|
||||
hashes.reserve(count);
|
||||
@@ -733,6 +774,29 @@ coro<> RemoteClient::rP_System(Net::AsyncSocket &sock) {
|
||||
auto lock = NetworkAndResource.lock();
|
||||
lock->NextRequest.Hashes.append_range(hashes);
|
||||
lock->ClientRequested.append_range(hashes);
|
||||
|
||||
if(debugRequestLogCount.fetch_add(1) < 64) {
|
||||
if(!hashes.empty()) {
|
||||
const auto& h = hashes.front();
|
||||
LOG.debug() << "ResourceRequest count=" << count
|
||||
<< " first=" << int(h[0]) << '.'
|
||||
<< int(h[1]) << '.'
|
||||
<< int(h[2]) << '.'
|
||||
<< int(h[3]);
|
||||
} else {
|
||||
LOG.debug() << "ResourceRequest count=" << count;
|
||||
}
|
||||
}
|
||||
co_return;
|
||||
}
|
||||
case ToServer::L2System::ReloadMods:
|
||||
{
|
||||
if(Server) {
|
||||
Server->requestModsReload();
|
||||
LOG.info() << "Запрос на перезагрузку модов";
|
||||
} else {
|
||||
LOG.warn() << "Запрос на перезагрузку модов отклонён: сервер не назначен";
|
||||
}
|
||||
co_return;
|
||||
}
|
||||
default:
|
||||
@@ -818,24 +882,59 @@ void RemoteClient::onUpdate() {
|
||||
// Отправка ресурсов
|
||||
if(!AssetsInWork.ToSend.empty()) {
|
||||
auto& toSend = AssetsInWork.ToSend;
|
||||
constexpr uint16_t kMaxAssetPacketSize = 64000;
|
||||
const size_t maxChunkPayload = std::max<size_t>(1, kMaxAssetPacketSize - 1 - 1 - 32 - 4);
|
||||
size_t chunkSize = std::max<size_t>(1'024'000 / toSend.size(), 4096);
|
||||
chunkSize = std::min(chunkSize, maxChunkPayload);
|
||||
static std::atomic<uint32_t> debugInitSendLogCount = 0;
|
||||
|
||||
Net::Packet& p = AssetsInWork.AssetsPacket;
|
||||
|
||||
auto flushAssetsPacket = [&]() {
|
||||
if(p.size() == 0)
|
||||
return;
|
||||
AssetsInWork.AssetsPackets.push_back(std::move(p));
|
||||
};
|
||||
|
||||
bool hasFullSended = false;
|
||||
|
||||
for(auto& [type, domain, key, id, res, sended] : toSend) {
|
||||
if(sended == 0) {
|
||||
// Оповещаем о начале отправки ресурса
|
||||
const size_t initSize = 1 + 1 + 4 + 32 + 4 + 1
|
||||
+ 2 + domain.size()
|
||||
+ 2 + key.size();
|
||||
if(p.size() + initSize > kMaxAssetPacketSize)
|
||||
flushAssetsPacket();
|
||||
p << (uint8_t) ToClient::L1::Resource
|
||||
<< (uint8_t) ToClient::L2Resource::InitResSend
|
||||
<< uint32_t(res.size());
|
||||
p.write((const std::byte*) res.hash().data(), 32);
|
||||
p << uint32_t(id) << uint8_t(type) << domain << key;
|
||||
if(domain == "test"
|
||||
&& (type == EnumAssets::Nodestate
|
||||
|| type == EnumAssets::Model
|
||||
|| type == EnumAssets::Texture))
|
||||
{
|
||||
if(debugInitSendLogCount.fetch_add(1) < 64) {
|
||||
const auto hash = res.hash();
|
||||
LOG.debug() << "Send InitResSend type=" << assetTypeName(type)
|
||||
<< " id=" << id
|
||||
<< " key=" << domain << ':' << key
|
||||
<< " size=" << res.size()
|
||||
<< " hash=" << int(hash[0]) << '.'
|
||||
<< int(hash[1]) << '.'
|
||||
<< int(hash[2]) << '.'
|
||||
<< int(hash[3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Отправляем чанк
|
||||
size_t willSend = std::min(chunkSize, res.size()-sended);
|
||||
const size_t chunkMsgSize = 1 + 1 + 32 + 4 + willSend;
|
||||
if(p.size() + chunkMsgSize > kMaxAssetPacketSize)
|
||||
flushAssetsPacket();
|
||||
p << (uint8_t) ToClient::L1::Resource
|
||||
<< (uint8_t) ToClient::L2Resource::ChunkSend;
|
||||
p.write((const std::byte*) res.hash().data(), 32);
|
||||
@@ -862,4 +961,4 @@ std::vector<std::tuple<WorldId_t, Pos::Object, uint8_t>> RemoteClient::getViewPo
|
||||
return {{0, CameraPos, 1}};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
namespace LV::Server {
|
||||
|
||||
class World;
|
||||
class GameServer;
|
||||
|
||||
template<typename ServerKey, typename ClientKey, std::enable_if_t<sizeof(ServerKey) >= sizeof(ClientKey), int> = 0>
|
||||
class CSChunkedMapper {
|
||||
@@ -316,6 +317,7 @@ class RemoteClient {
|
||||
// Тип, домен, ключ, идентификатор, ресурс, количество отправленных байт
|
||||
std::vector<std::tuple<EnumAssets, std::string, std::string, ResourceId, Resource, size_t>> ToSend;
|
||||
// Пакет с ресурсами
|
||||
std::vector<Net::Packet> AssetsPackets;
|
||||
Net::Packet AssetsPacket;
|
||||
} AssetsInWork;
|
||||
|
||||
@@ -336,8 +338,8 @@ public:
|
||||
std::queue<Pos::GlobalNode> Build, Break;
|
||||
|
||||
public:
|
||||
RemoteClient(asio::io_context &ioc, tcp::socket socket, const std::string username)
|
||||
: LOG("RemoteClient " + username), Socket(ioc, std::move(socket)), Username(username)
|
||||
RemoteClient(asio::io_context &ioc, tcp::socket socket, const std::string username, GameServer* server)
|
||||
: LOG("RemoteClient " + username), Socket(ioc, std::move(socket)), Username(username), Server(server)
|
||||
{}
|
||||
|
||||
~RemoteClient();
|
||||
@@ -434,6 +436,7 @@ public:
|
||||
void onUpdate();
|
||||
|
||||
private:
|
||||
GameServer* Server = nullptr;
|
||||
void protocolError();
|
||||
coro<> readPacket(Net::AsyncSocket &sock);
|
||||
coro<> rP_System(Net::AsyncSocket &sock);
|
||||
@@ -447,4 +450,4 @@ private:
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user