codex-5.2: синхронизация ресурсов модов, частичная перезагрузка модов
This commit is contained in:
@@ -8,6 +8,7 @@
|
|||||||
#include "glm/ext/quaternion_geometric.hpp"
|
#include "glm/ext/quaternion_geometric.hpp"
|
||||||
#include <GLFW/glfw3.h>
|
#include <GLFW/glfw3.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <atomic>
|
||||||
#include <boost/asio/deadline_timer.hpp>
|
#include <boost/asio/deadline_timer.hpp>
|
||||||
#include <boost/asio/this_coro.hpp>
|
#include <boost/asio/this_coro.hpp>
|
||||||
#include <boost/date_time/posix_time/posix_time_duration.hpp>
|
#include <boost/date_time/posix_time/posix_time_duration.hpp>
|
||||||
@@ -15,12 +16,93 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <Common/Packets.hpp>
|
#include <Common/Packets.hpp>
|
||||||
#include <glm/ext.hpp>
|
#include <glm/ext.hpp>
|
||||||
|
#include <optional>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|
||||||
|
|
||||||
namespace LV::Client {
|
namespace LV::Client {
|
||||||
|
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<DefNodeId> debugExpectedGeneratedNodeId(int rx, int ry, int rz) {
|
||||||
|
if(ry == 1 && rz == 0)
|
||||||
|
return DefNodeId(0);
|
||||||
|
if(rx == 0 && ry == 1)
|
||||||
|
return DefNodeId(0);
|
||||||
|
if(rx == 0 && rz == 0)
|
||||||
|
return DefNodeId(1);
|
||||||
|
if(ry == 0 && rz == 0)
|
||||||
|
return DefNodeId(2);
|
||||||
|
if(rx == 0 && ry == 0)
|
||||||
|
return DefNodeId(3);
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void debugCheckGeneratedChunkNodes(WorldId_t worldId,
|
||||||
|
Pos::GlobalChunk chunkPos,
|
||||||
|
const std::array<Node, 16 * 16 * 16>& chunk)
|
||||||
|
{
|
||||||
|
if(chunkPos[0] != 0 && chunkPos[1] != 0 && chunkPos[2] != 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
static std::atomic<uint32_t> warnCount = 0;
|
||||||
|
if(warnCount.load() >= 16)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Pos::bvec4u localChunk = chunkPos & 0x3;
|
||||||
|
const int baseX = int(localChunk[0]) * 16;
|
||||||
|
const int baseY = int(localChunk[1]) * 16;
|
||||||
|
const int baseZ = int(localChunk[2]) * 16;
|
||||||
|
const int globalBaseX = int(chunkPos[0]) * 16;
|
||||||
|
const int globalBaseY = int(chunkPos[1]) * 16;
|
||||||
|
const int globalBaseZ = int(chunkPos[2]) * 16;
|
||||||
|
|
||||||
|
for(int z = 0; z < 16; z++)
|
||||||
|
for(int y = 0; y < 16; y++)
|
||||||
|
for(int x = 0; x < 16; x++) {
|
||||||
|
int rx = baseX + x;
|
||||||
|
int ry = baseY + y;
|
||||||
|
int rz = baseZ + z;
|
||||||
|
int gx = globalBaseX + x;
|
||||||
|
int gy = globalBaseY + y;
|
||||||
|
int gz = globalBaseZ + z;
|
||||||
|
std::optional<DefNodeId> expected = debugExpectedGeneratedNodeId(rx, ry, rz);
|
||||||
|
if(!expected)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const Node& node = chunk[x + y * 16 + z * 16 * 16];
|
||||||
|
if(node.NodeId != *expected) {
|
||||||
|
uint32_t index = warnCount.fetch_add(1);
|
||||||
|
if(index < 16) {
|
||||||
|
TOS::Logger("Client>WorldDebug").warn()
|
||||||
|
<< "Generated node mismatch world " << worldId
|
||||||
|
<< " chunk " << int(chunkPos[0]) << ',' << int(chunkPos[1]) << ',' << int(chunkPos[2])
|
||||||
|
<< " at local " << rx << ',' << ry << ',' << rz
|
||||||
|
<< " global " << gx << ',' << gy << ',' << gz
|
||||||
|
<< " expected " << *expected
|
||||||
|
<< " got " << node.NodeId;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
ServerSession::ServerSession(asio::io_context &ioc, std::unique_ptr<Net::AsyncSocket>&& socket)
|
ServerSession::ServerSession(asio::io_context &ioc, std::unique_ptr<Net::AsyncSocket>&& socket)
|
||||||
: IAsyncDestructible(ioc), Socket(std::move(socket)) //, NetInputPackets(1024)
|
: IAsyncDestructible(ioc), Socket(std::move(socket)) //, NetInputPackets(1024)
|
||||||
{
|
{
|
||||||
@@ -170,6 +252,18 @@ void ServerSession::shutdown(EnumDisconnect type) {
|
|||||||
LOG.info() << "Отключение от сервера: " << reason;
|
LOG.info() << "Отключение от сервера: " << reason;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ServerSession::requestModsReload() {
|
||||||
|
if(!Socket || !isConnected())
|
||||||
|
return;
|
||||||
|
|
||||||
|
Net::Packet packet;
|
||||||
|
packet << (uint8_t) ToServer::L1::System
|
||||||
|
<< (uint8_t) ToServer::L2System::ReloadMods;
|
||||||
|
|
||||||
|
Socket->pushPacket(std::move(packet));
|
||||||
|
LOG.info() << "Запрос на перезагрузку модов отправлен";
|
||||||
|
}
|
||||||
|
|
||||||
void ServerSession::onResize(uint32_t width, uint32_t height) {
|
void ServerSession::onResize(uint32_t width, uint32_t height) {
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -284,10 +378,50 @@ void ServerSession::update(GlobalTime gTime, float dTime) {
|
|||||||
|
|
||||||
// Получить ресурсы с AssetsManager
|
// Получить ресурсы с AssetsManager
|
||||||
{
|
{
|
||||||
|
static std::atomic<uint32_t> debugAssetReadLogCount = 0;
|
||||||
std::vector<std::pair<AssetsManager::ResourceKey, std::optional<Resource>>> resources = AM->pullReads();
|
std::vector<std::pair<AssetsManager::ResourceKey, std::optional<Resource>>> resources = AM->pullReads();
|
||||||
std::vector<Hash_t> needRequest;
|
std::vector<Hash_t> needRequest;
|
||||||
|
|
||||||
for(auto& [key, res] : resources) {
|
for(auto& [key, res] : resources) {
|
||||||
|
{
|
||||||
|
auto& waitingByDomain = AsyncContext.ResourceWait[(int) key.Type];
|
||||||
|
auto iterDomain = waitingByDomain.find(key.Domain);
|
||||||
|
if(iterDomain != waitingByDomain.end()) {
|
||||||
|
auto& entries = iterDomain->second;
|
||||||
|
entries.erase(std::remove_if(entries.begin(), entries.end(),
|
||||||
|
[&](const std::pair<std::string, Hash_t>& entry) {
|
||||||
|
return entry.first == key.Key && entry.second == key.Hash;
|
||||||
|
}),
|
||||||
|
entries.end());
|
||||||
|
if(entries.empty())
|
||||||
|
waitingByDomain.erase(iterDomain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(key.Domain == "test"
|
||||||
|
&& (key.Type == EnumAssets::Nodestate
|
||||||
|
|| key.Type == EnumAssets::Model
|
||||||
|
|| key.Type == EnumAssets::Texture))
|
||||||
|
{
|
||||||
|
uint32_t idx = debugAssetReadLogCount.fetch_add(1);
|
||||||
|
if(idx < 128) {
|
||||||
|
if(res) {
|
||||||
|
LOG.debug() << "Cache hit type=" << assetTypeName(key.Type)
|
||||||
|
<< " id=" << key.Id
|
||||||
|
<< " key=" << key.Domain << ':' << key.Key
|
||||||
|
<< " size=" << res->size();
|
||||||
|
} else {
|
||||||
|
LOG.debug() << "Cache miss type=" << assetTypeName(key.Type)
|
||||||
|
<< " id=" << key.Id
|
||||||
|
<< " key=" << key.Domain << ':' << key.Key
|
||||||
|
<< " hash=" << int(key.Hash[0]) << '.'
|
||||||
|
<< int(key.Hash[1]) << '.'
|
||||||
|
<< int(key.Hash[2]) << '.'
|
||||||
|
<< int(key.Hash[3]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(!res) {
|
if(!res) {
|
||||||
// Проверить не был ли уже отправлен запрос на получение этого хеша
|
// Проверить не был ли уже отправлен запрос на получение этого хеша
|
||||||
auto iter = std::lower_bound(AsyncContext.AlreadyLoading.begin(), AsyncContext.AlreadyLoading.end(), key.Hash);
|
auto iter = std::lower_bound(AsyncContext.AlreadyLoading.begin(), AsyncContext.AlreadyLoading.end(), key.Hash);
|
||||||
@@ -311,6 +445,11 @@ void ServerSession::update(GlobalTime gTime, float dTime) {
|
|||||||
if(!needRequest.empty()) {
|
if(!needRequest.empty()) {
|
||||||
assert(needRequest.size() < (1 << 16));
|
assert(needRequest.size() < (1 << 16));
|
||||||
|
|
||||||
|
uint32_t idx = debugAssetReadLogCount.fetch_add(1);
|
||||||
|
if(idx < 128) {
|
||||||
|
LOG.debug() << "Send ResourceRequest count=" << needRequest.size();
|
||||||
|
}
|
||||||
|
|
||||||
Net::Packet p;
|
Net::Packet p;
|
||||||
p << (uint8_t) ToServer::L1::System << (uint8_t) ToServer::L2System::ResourceRequest;
|
p << (uint8_t) ToServer::L1::System << (uint8_t) ToServer::L2System::ResourceRequest;
|
||||||
p << (uint16_t) needRequest.size();
|
p << (uint16_t) needRequest.size();
|
||||||
@@ -416,8 +555,33 @@ void ServerSession::update(GlobalTime gTime, float dTime) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Отправляем запрос на получение ресурсов
|
// Отправляем запрос на получение ресурсов
|
||||||
if(!needToLoad.empty())
|
if(!needToLoad.empty()) {
|
||||||
|
static std::atomic<uint32_t> debugReadRequestLogCount = 0;
|
||||||
|
AssetsManager::ResourceKey firstDebug;
|
||||||
|
bool hasDebug = false;
|
||||||
|
for(const auto& entry : needToLoad) {
|
||||||
|
if(entry.Domain == "test"
|
||||||
|
&& (entry.Type == EnumAssets::Nodestate
|
||||||
|
|| entry.Type == EnumAssets::Model
|
||||||
|
|| entry.Type == EnumAssets::Texture))
|
||||||
|
{
|
||||||
|
firstDebug = entry;
|
||||||
|
hasDebug = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(hasDebug && debugReadRequestLogCount.fetch_add(1) < 64) {
|
||||||
|
LOG.debug() << "Queue asset read count=" << needToLoad.size()
|
||||||
|
<< " type=" << assetTypeName(firstDebug.Type)
|
||||||
|
<< " id=" << firstDebug.Id
|
||||||
|
<< " key=" << firstDebug.Domain << ':' << firstDebug.Key
|
||||||
|
<< " hash=" << int(firstDebug.Hash[0]) << '.'
|
||||||
|
<< int(firstDebug.Hash[1]) << '.'
|
||||||
|
<< int(firstDebug.Hash[2]) << '.'
|
||||||
|
<< int(firstDebug.Hash[3]);
|
||||||
|
}
|
||||||
AM->pushReads(std::move(needToLoad));
|
AM->pushReads(std::move(needToLoad));
|
||||||
|
}
|
||||||
|
|
||||||
AsyncContext.Binds.push_back(std::move(abc));
|
AsyncContext.Binds.push_back(std::move(abc));
|
||||||
}
|
}
|
||||||
@@ -675,7 +839,9 @@ void ServerSession::update(GlobalTime gTime, float dTime) {
|
|||||||
auto& c = chunks_Changed[wId];
|
auto& c = chunks_Changed[wId];
|
||||||
|
|
||||||
for(auto& [pos, val] : list) {
|
for(auto& [pos, val] : list) {
|
||||||
unCompressNodes(val, caocvr[pos].data());
|
auto& chunkNodes = caocvr[pos];
|
||||||
|
unCompressNodes(val, chunkNodes.data());
|
||||||
|
debugCheckGeneratedChunkNodes(wId, pos, chunkNodes);
|
||||||
c.push_back(pos);
|
c.push_back(pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -940,6 +1106,19 @@ void ServerSession::setRenderSession(IRenderSession* session) {
|
|||||||
RS = session;
|
RS = session;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ServerSession::resetResourceSyncState() {
|
||||||
|
AsyncContext.AssetsLoading.clear();
|
||||||
|
AsyncContext.AlreadyLoading.clear();
|
||||||
|
for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++)
|
||||||
|
AsyncContext.ResourceWait[type].clear();
|
||||||
|
AsyncContext.Binds.clear();
|
||||||
|
AsyncContext.LoadedResources.clear();
|
||||||
|
AsyncContext.ThisTickEntry = {};
|
||||||
|
AsyncContext.LoadedAssets.lock()->clear();
|
||||||
|
AsyncContext.AssetsBinds.lock()->clear();
|
||||||
|
AsyncContext.TickSequence.lock()->clear();
|
||||||
|
}
|
||||||
|
|
||||||
coro<> ServerSession::run(AsyncUseControl::Lock) {
|
coro<> ServerSession::run(AsyncUseControl::Lock) {
|
||||||
try {
|
try {
|
||||||
while(!IsGoingShutdown && IsConnected) {
|
while(!IsGoingShutdown && IsConnected) {
|
||||||
@@ -950,6 +1129,7 @@ coro<> ServerSession::run(AsyncUseControl::Lock) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
IsConnected = false;
|
IsConnected = false;
|
||||||
|
resetResourceSyncState();
|
||||||
|
|
||||||
co_return;
|
co_return;
|
||||||
}
|
}
|
||||||
@@ -1009,6 +1189,7 @@ coro<> ServerSession::rP_System(Net::AsyncSocket &sock) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
coro<> ServerSession::rP_Resource(Net::AsyncSocket &sock) {
|
coro<> ServerSession::rP_Resource(Net::AsyncSocket &sock) {
|
||||||
|
static std::atomic<uint32_t> debugResourceLogCount = 0;
|
||||||
uint8_t second = co_await sock.read<uint8_t>();
|
uint8_t second = co_await sock.read<uint8_t>();
|
||||||
|
|
||||||
switch((ToClient::L2Resource) second) {
|
switch((ToClient::L2Resource) second) {
|
||||||
@@ -1035,6 +1216,23 @@ coro<> ServerSession::rP_Resource(Net::AsyncSocket &sock) {
|
|||||||
(EnumAssets) type, (ResourceId) id, std::move(domain),
|
(EnumAssets) type, (ResourceId) id, std::move(domain),
|
||||||
std::move(key), hash
|
std::move(key), hash
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if(binds.back().Domain == "test"
|
||||||
|
&& (binds.back().Type == EnumAssets::Nodestate
|
||||||
|
|| binds.back().Type == EnumAssets::Model
|
||||||
|
|| binds.back().Type == EnumAssets::Texture))
|
||||||
|
{
|
||||||
|
uint32_t idx = debugResourceLogCount.fetch_add(1);
|
||||||
|
if(idx < 128) {
|
||||||
|
LOG.debug() << "Bind asset type=" << assetTypeName(binds.back().Type)
|
||||||
|
<< " id=" << binds.back().Id
|
||||||
|
<< " key=" << binds.back().Domain << ':' << binds.back().Key
|
||||||
|
<< " hash=" << int(binds.back().Hash[0]) << '.'
|
||||||
|
<< int(binds.back().Hash[1]) << '.'
|
||||||
|
<< int(binds.back().Hash[2]) << '.'
|
||||||
|
<< int(binds.back().Hash[3]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncContext.AssetsBinds.lock()->push_back(AssetsBindsChange(binds, {}));
|
AsyncContext.AssetsBinds.lock()->push_back(AssetsBindsChange(binds, {}));
|
||||||
@@ -1072,6 +1270,20 @@ coro<> ServerSession::rP_Resource(Net::AsyncSocket &sock) {
|
|||||||
std::string domain = co_await sock.read<std::string>();
|
std::string domain = co_await sock.read<std::string>();
|
||||||
std::string key = co_await sock.read<std::string>();
|
std::string key = co_await sock.read<std::string>();
|
||||||
|
|
||||||
|
if(domain == "test"
|
||||||
|
&& (type == EnumAssets::Nodestate
|
||||||
|
|| type == EnumAssets::Model
|
||||||
|
|| type == EnumAssets::Texture))
|
||||||
|
{
|
||||||
|
uint32_t idx = debugResourceLogCount.fetch_add(1);
|
||||||
|
if(idx < 128) {
|
||||||
|
LOG.debug() << "InitResSend type=" << assetTypeName(type)
|
||||||
|
<< " id=" << id
|
||||||
|
<< " key=" << domain << ':' << key
|
||||||
|
<< " size=" << size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
AsyncContext.AssetsLoading[hash] = AssetLoading{
|
AsyncContext.AssetsLoading[hash] = AssetLoading{
|
||||||
type, id, std::move(domain), std::move(key),
|
type, id, std::move(domain), std::move(key),
|
||||||
std::u8string(size, '\0'), 0
|
std::u8string(size, '\0'), 0
|
||||||
@@ -1095,6 +1307,20 @@ coro<> ServerSession::rP_Resource(Net::AsyncSocket &sock) {
|
|||||||
|
|
||||||
if(al.Offset == al.Data.size()) {
|
if(al.Offset == al.Data.size()) {
|
||||||
// Ресурс полностью загружен
|
// Ресурс полностью загружен
|
||||||
|
if(al.Domain == "test"
|
||||||
|
&& (al.Type == EnumAssets::Nodestate
|
||||||
|
|| al.Type == EnumAssets::Model
|
||||||
|
|| al.Type == EnumAssets::Texture))
|
||||||
|
{
|
||||||
|
uint32_t idx = debugResourceLogCount.fetch_add(1);
|
||||||
|
if(idx < 128) {
|
||||||
|
LOG.debug() << "Resource loaded type=" << assetTypeName(al.Type)
|
||||||
|
<< " id=" << al.Id
|
||||||
|
<< " key=" << al.Domain << ':' << al.Key
|
||||||
|
<< " size=" << al.Data.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
AsyncContext.LoadedAssets.lock()->emplace_back(
|
AsyncContext.LoadedAssets.lock()->emplace_back(
|
||||||
al.Type, al.Id, std::move(al.Domain), std::move(al.Key), std::move(al.Data)
|
al.Type, al.Id, std::move(al.Domain), std::move(al.Key), std::move(al.Data)
|
||||||
);
|
);
|
||||||
@@ -1118,6 +1344,7 @@ coro<> ServerSession::rP_Resource(Net::AsyncSocket &sock) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
coro<> ServerSession::rP_Definition(Net::AsyncSocket &sock) {
|
coro<> ServerSession::rP_Definition(Net::AsyncSocket &sock) {
|
||||||
|
static std::atomic<uint32_t> debugDefLogCount = 0;
|
||||||
uint8_t second = co_await sock.read<uint8_t>();
|
uint8_t second = co_await sock.read<uint8_t>();
|
||||||
|
|
||||||
switch((ToClient::L2Definition) second) {
|
switch((ToClient::L2Definition) second) {
|
||||||
@@ -1148,6 +1375,15 @@ coro<> ServerSession::rP_Definition(Net::AsyncSocket &sock) {
|
|||||||
def.NodestateId = co_await sock.read<uint32_t>();
|
def.NodestateId = co_await sock.read<uint32_t>();
|
||||||
def.TexId = id;
|
def.TexId = id;
|
||||||
|
|
||||||
|
if(id < 32) {
|
||||||
|
uint32_t idx = debugDefLogCount.fetch_add(1);
|
||||||
|
if(idx < 64) {
|
||||||
|
LOG.debug() << "DefNode id=" << id
|
||||||
|
<< " nodestate=" << def.NodestateId
|
||||||
|
<< " tex=" << def.TexId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
AsyncContext.ThisTickEntry.Profile_Node_AddOrChange.emplace_back(id, def);
|
AsyncContext.ThisTickEntry.Profile_Node_AddOrChange.emplace_back(id, def);
|
||||||
|
|
||||||
co_return;
|
co_return;
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ public:
|
|||||||
static coro<std::unique_ptr<Net::AsyncSocket>> asyncInitGameProtocol(asio::io_context &ioc, tcp::socket &&socket, std::function<void(const std::string&)> onProgress = nullptr);
|
static coro<std::unique_ptr<Net::AsyncSocket>> asyncInitGameProtocol(asio::io_context &ioc, tcp::socket &&socket, std::function<void(const std::string&)> onProgress = nullptr);
|
||||||
|
|
||||||
void shutdown(EnumDisconnect type);
|
void shutdown(EnumDisconnect type);
|
||||||
|
void requestModsReload();
|
||||||
|
|
||||||
bool isConnected() {
|
bool isConnected() {
|
||||||
return Socket->isAlive() && IsConnected;
|
return Socket->isAlive() && IsConnected;
|
||||||
@@ -182,6 +183,7 @@ private:
|
|||||||
ServerSession(asio::io_context &ioc, std::unique_ptr<Net::AsyncSocket> &&socket);
|
ServerSession(asio::io_context &ioc, std::unique_ptr<Net::AsyncSocket> &&socket);
|
||||||
|
|
||||||
virtual coro<> asyncDestructor() override;
|
virtual coro<> asyncDestructor() override;
|
||||||
|
void resetResourceSyncState();
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2305,6 +2305,10 @@ void Vulkan::gui_ConnectedToServer() {
|
|||||||
if(ImGui::Button("Delimeter"))
|
if(ImGui::Button("Delimeter"))
|
||||||
LOG.debug();
|
LOG.debug();
|
||||||
|
|
||||||
|
if(ImGui::Button("Перезагрузить моды")) {
|
||||||
|
Game.Session->requestModsReload();
|
||||||
|
}
|
||||||
|
|
||||||
if(ImGui::Button("Выйти")) {
|
if(ImGui::Button("Выйти")) {
|
||||||
Game.Выйти = true;
|
Game.Выйти = true;
|
||||||
Game.ImGuiInterfaces.pop_back();
|
Game.ImGuiInterfaces.pop_back();
|
||||||
|
|||||||
@@ -9,11 +9,13 @@
|
|||||||
#include "glm/ext/scalar_constants.hpp"
|
#include "glm/ext/scalar_constants.hpp"
|
||||||
#include "glm/matrix.hpp"
|
#include "glm/matrix.hpp"
|
||||||
#include "glm/trigonometric.hpp"
|
#include "glm/trigonometric.hpp"
|
||||||
|
#include <atomic>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -307,6 +309,15 @@ void ChunkMeshGenerator::run(uint8_t id) {
|
|||||||
NodeVertexStatic v;
|
NodeVertexStatic v;
|
||||||
std::memset(&v, 0, sizeof(v));
|
std::memset(&v, 0, sizeof(v));
|
||||||
|
|
||||||
|
static std::atomic<uint32_t> debugMeshWarnCount = 0;
|
||||||
|
const bool debugMeshEnabled = debugMeshWarnCount.load() < 16;
|
||||||
|
std::array<uint8_t, 16> expectedColumnX = {};
|
||||||
|
std::array<uint8_t, 16> expectedColumnY = {};
|
||||||
|
std::array<uint8_t, 16> expectedColumnZ = {};
|
||||||
|
std::array<uint8_t, 16> generatedColumnX = {};
|
||||||
|
std::array<uint8_t, 16> generatedColumnY = {};
|
||||||
|
std::array<uint8_t, 16> generatedColumnZ = {};
|
||||||
|
|
||||||
struct ModelCacheEntry {
|
struct ModelCacheEntry {
|
||||||
std::vector<std::vector<std::pair<float, std::unordered_map<EnumFace, std::vector<NodeVertexStatic>>>>> Routes;
|
std::vector<std::vector<std::pair<float, std::unordered_map<EnumFace, std::vector<NodeVertexStatic>>>>> Routes;
|
||||||
};
|
};
|
||||||
@@ -377,6 +388,7 @@ void ChunkMeshGenerator::run(uint8_t id) {
|
|||||||
for(int z = 0; z < 16; z++)
|
for(int z = 0; z < 16; z++)
|
||||||
for(int y = 0; y < 16; y++)
|
for(int y = 0; y < 16; y++)
|
||||||
for(int x = 0; x < 16; x++) {
|
for(int x = 0; x < 16; x++) {
|
||||||
|
const size_t vertexStart = result.NodeVertexs.size();
|
||||||
int fullCovered = 0;
|
int fullCovered = 0;
|
||||||
|
|
||||||
fullCovered |= fullNodes[x+1+1][y+1][z+1];
|
fullCovered |= fullNodes[x+1+1][y+1][z+1];
|
||||||
@@ -392,9 +404,18 @@ void ChunkMeshGenerator::run(uint8_t id) {
|
|||||||
const Node& nodeData = (*chunk)[x+y*16+z*16*16];
|
const Node& nodeData = (*chunk)[x+y*16+z*16*16];
|
||||||
const DefNode_t* node = getNodeProfile(nodeData.NodeId);
|
const DefNode_t* node = getNodeProfile(nodeData.NodeId);
|
||||||
|
|
||||||
|
if(debugMeshEnabled) {
|
||||||
|
const bool hasRenderable = (node->NodestateId != 0) || (node->TexId != 0);
|
||||||
|
if(hasRenderable && fullCovered != 0b111111) {
|
||||||
|
expectedColumnX[x] = 1;
|
||||||
|
expectedColumnY[y] = 1;
|
||||||
|
expectedColumnZ[z] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool usedModel = false;
|
bool usedModel = false;
|
||||||
|
|
||||||
if(NSP && node->NodestateId != 0) {
|
if(NSP && (node->NodestateId != 0 || NSP->hasNodestate(node->NodestateId))) {
|
||||||
auto iterCache = modelCache.find(nodeData.Data);
|
auto iterCache = modelCache.find(nodeData.Data);
|
||||||
if(iterCache == modelCache.end()) {
|
if(iterCache == modelCache.end()) {
|
||||||
std::unordered_map<std::string, int32_t> states;
|
std::unordered_map<std::string, int32_t> states;
|
||||||
@@ -423,7 +444,7 @@ void ChunkMeshGenerator::run(uint8_t id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(usedModel)
|
if(usedModel)
|
||||||
continue;
|
goto node_done;
|
||||||
|
|
||||||
if(NSP && node->TexId != 0) {
|
if(NSP && node->TexId != 0) {
|
||||||
auto iterTex = baseTextureCache.find(node->TexId);
|
auto iterTex = baseTextureCache.find(node->TexId);
|
||||||
@@ -439,7 +460,7 @@ void ChunkMeshGenerator::run(uint8_t id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(v.Tex == 0)
|
if(v.Tex == 0)
|
||||||
continue;
|
goto node_done;
|
||||||
|
|
||||||
// Рендерим обычный кубоид
|
// Рендерим обычный кубоид
|
||||||
// XZ+Y
|
// XZ+Y
|
||||||
@@ -645,6 +666,60 @@ void ChunkMeshGenerator::run(uint8_t id) {
|
|||||||
v.TV = 0;
|
v.TV = 0;
|
||||||
result.NodeVertexs.push_back(v);
|
result.NodeVertexs.push_back(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
node_done:
|
||||||
|
if(debugMeshEnabled) {
|
||||||
|
const bool emitted = result.NodeVertexs.size() > vertexStart;
|
||||||
|
if(emitted) {
|
||||||
|
generatedColumnX[x] = 1;
|
||||||
|
generatedColumnY[y] = 1;
|
||||||
|
generatedColumnZ[z] = 1;
|
||||||
|
} else {
|
||||||
|
const bool hasRenderable = (node->NodestateId != 0) || (node->TexId != 0);
|
||||||
|
if(hasRenderable && fullCovered != 0b111111) {
|
||||||
|
uint32_t warnIndex = debugMeshWarnCount.fetch_add(1);
|
||||||
|
if(warnIndex < 16) {
|
||||||
|
LOG.warn() << "Missing node geometry at chunk " << int(pos[0]) << ','
|
||||||
|
<< int(pos[1]) << ',' << int(pos[2])
|
||||||
|
<< " local " << x << ',' << y << ',' << z
|
||||||
|
<< " nodeId " << nodeData.NodeId
|
||||||
|
<< " meta " << int(nodeData.Meta)
|
||||||
|
<< " covered " << fullCovered
|
||||||
|
<< " tex " << node->TexId
|
||||||
|
<< " nodestate " << node->NodestateId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(debugMeshEnabled) {
|
||||||
|
auto collectMissing = [](const std::array<uint8_t, 16>& expected,
|
||||||
|
const std::array<uint8_t, 16>& generated) {
|
||||||
|
std::string res;
|
||||||
|
for(int i = 0; i < 16; i++) {
|
||||||
|
if(expected[i] && !generated[i]) {
|
||||||
|
if(!res.empty())
|
||||||
|
res += ',';
|
||||||
|
res += std::to_string(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string missingX = collectMissing(expectedColumnX, generatedColumnX);
|
||||||
|
std::string missingY = collectMissing(expectedColumnY, generatedColumnY);
|
||||||
|
std::string missingZ = collectMissing(expectedColumnZ, generatedColumnZ);
|
||||||
|
if(!missingX.empty() || !missingY.empty() || !missingZ.empty()) {
|
||||||
|
uint32_t warnIndex = debugMeshWarnCount.fetch_add(1);
|
||||||
|
if(warnIndex < 16) {
|
||||||
|
LOG.warn() << "Missing mesh columns at chunk " << int(pos[0]) << ','
|
||||||
|
<< int(pos[1]) << ',' << int(pos[2])
|
||||||
|
<< " missingX[" << missingX << "]"
|
||||||
|
<< " missingY[" << missingY << "]"
|
||||||
|
<< " missingZ[" << missingZ << "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Вычислить индексы и сократить вершины
|
// Вычислить индексы и сократить вершины
|
||||||
@@ -1589,8 +1664,10 @@ void VulkanRenderSession::tickSync(const TickSyncData& data) {
|
|||||||
modelLost.insert(modelLost.end(), iter->second.begin(), iter->second.end());
|
modelLost.insert(modelLost.end(), iter->second.begin(), iter->second.end());
|
||||||
|
|
||||||
std::vector<AssetsModel> changedModels;
|
std::vector<AssetsModel> changedModels;
|
||||||
if(!modelResources.empty() || !modelLost.empty())
|
if(!modelResources.empty() || !modelLost.empty()) {
|
||||||
changedModels = MP.onModelChanges(std::move(modelResources), std::move(modelLost));
|
const auto& modelAssets = ServerSession->Assets[EnumAssets::Model];
|
||||||
|
changedModels = MP.onModelChanges(std::move(modelResources), std::move(modelLost), &modelAssets);
|
||||||
|
}
|
||||||
|
|
||||||
if(TP) {
|
if(TP) {
|
||||||
std::vector<std::tuple<AssetsTexture, Resource>> textureResources;
|
std::vector<std::tuple<AssetsTexture, Resource>> textureResources;
|
||||||
|
|||||||
@@ -85,7 +85,9 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Применяет изменения, возвращая все затронутые модели
|
// Применяет изменения, возвращая все затронутые модели
|
||||||
std::vector<AssetsModel> onModelChanges(std::vector<std::tuple<AssetsModel, Resource>> newOrChanged, std::vector<AssetsModel> lost) {
|
std::vector<AssetsModel> onModelChanges(std::vector<std::tuple<AssetsModel, Resource>> newOrChanged,
|
||||||
|
std::vector<AssetsModel> lost,
|
||||||
|
const std::unordered_map<ResourceId, AssetEntry>* modelAssets) {
|
||||||
std::vector<AssetsModel> result;
|
std::vector<AssetsModel> result;
|
||||||
|
|
||||||
std::move_only_function<void(ResourceId)> makeUnready;
|
std::move_only_function<void(ResourceId)> makeUnready;
|
||||||
@@ -130,6 +132,14 @@ public:
|
|||||||
Models.erase(iterModel);
|
Models.erase(iterModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unordered_map<std::string, ResourceId> modelKeyToId;
|
||||||
|
if(modelAssets) {
|
||||||
|
modelKeyToId.reserve(modelAssets->size());
|
||||||
|
for(const auto& [id, entry] : *modelAssets) {
|
||||||
|
modelKeyToId.emplace(entry.Domain + ':' + entry.Key, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for(const auto& [key, resource] : newOrChanged) {
|
for(const auto& [key, resource] : newOrChanged) {
|
||||||
result.push_back(key);
|
result.push_back(key);
|
||||||
|
|
||||||
@@ -165,55 +175,58 @@ public:
|
|||||||
|
|
||||||
std::vector<Vertex> v;
|
std::vector<Vertex> v;
|
||||||
|
|
||||||
|
auto addQuad = [&](const glm::vec3& p0,
|
||||||
|
const glm::vec3& p1,
|
||||||
|
const glm::vec3& p2,
|
||||||
|
const glm::vec3& p3,
|
||||||
|
const glm::vec2& uv0,
|
||||||
|
const glm::vec2& uv1,
|
||||||
|
const glm::vec2& uv2,
|
||||||
|
const glm::vec2& uv3) {
|
||||||
|
v.emplace_back(p0, uv0, texId);
|
||||||
|
v.emplace_back(p1, uv1, texId);
|
||||||
|
v.emplace_back(p2, uv2, texId);
|
||||||
|
v.emplace_back(p0, uv0, texId);
|
||||||
|
v.emplace_back(p2, uv2, texId);
|
||||||
|
v.emplace_back(p3, uv3, texId);
|
||||||
|
};
|
||||||
|
|
||||||
|
const float x0 = min.x;
|
||||||
|
const float x1 = max.x;
|
||||||
|
const float y0 = min.y;
|
||||||
|
const float y1 = max.y;
|
||||||
|
const float z0 = min.z;
|
||||||
|
const float z1 = max.z;
|
||||||
|
const float u0 = from_uv.x;
|
||||||
|
const float v0 = from_uv.y;
|
||||||
|
const float u1 = to_uv.x;
|
||||||
|
const float v1 = to_uv.y;
|
||||||
|
|
||||||
switch(face) {
|
switch(face) {
|
||||||
case EnumFace::Down:
|
|
||||||
v.emplace_back(glm::vec3{min.x, min.y, min.z}, from_uv, texId);
|
|
||||||
v.emplace_back(glm::vec3{max.x, min.y, max.z}, glm::vec2{from_uv.x, to_uv.y}, texId);
|
|
||||||
v.emplace_back(glm::vec3{max.x, min.y, min.z}, to_uv, texId);
|
|
||||||
v.emplace_back(glm::vec3{min.x, min.y, min.z}, from_uv, texId);
|
|
||||||
v.emplace_back(glm::vec3{min.x, min.y, max.z}, to_uv, texId);
|
|
||||||
v.emplace_back(glm::vec3{max.x, min.y, max.z}, glm::vec2{to_uv.x, from_uv.y}, texId);
|
|
||||||
break;
|
|
||||||
case EnumFace::Up:
|
case EnumFace::Up:
|
||||||
v.emplace_back(glm::vec3{min.x, max.y, min.z}, from_uv, texId);
|
addQuad({x0, y1, z1}, {x1, y1, z1}, {x1, y1, z0}, {x0, y1, z0},
|
||||||
v.emplace_back(glm::vec3{max.x, max.y, min.z}, glm::vec2{from_uv.x, to_uv.y}, texId);
|
{u0, v0}, {u1, v0}, {u1, v1}, {u0, v1});
|
||||||
v.emplace_back(glm::vec3{max.x, max.y, max.z}, to_uv, texId);
|
break;
|
||||||
v.emplace_back(glm::vec3{min.x, max.y, min.z}, from_uv, texId);
|
case EnumFace::Down:
|
||||||
v.emplace_back(glm::vec3{max.x, max.y, max.z}, to_uv, texId);
|
addQuad({x0, y0, z1}, {x0, y0, z0}, {x1, y0, z0}, {x1, y0, z1},
|
||||||
v.emplace_back(glm::vec3{min.x, max.y, max.z}, glm::vec2{to_uv.x, from_uv.y}, texId);
|
{u0, v0}, {u0, v1}, {u1, v1}, {u1, v0});
|
||||||
break;
|
break;
|
||||||
case EnumFace::North:
|
|
||||||
v.emplace_back(glm::vec3{min.x, min.y, min.z}, from_uv, texId);
|
|
||||||
v.emplace_back(glm::vec3{max.x, min.y, min.z}, glm::vec2{from_uv.x, to_uv.y}, texId);
|
|
||||||
v.emplace_back(glm::vec3{max.x, max.y, min.z}, to_uv, texId);
|
|
||||||
v.emplace_back(glm::vec3{min.x, min.y, min.z}, from_uv, texId);
|
|
||||||
v.emplace_back(glm::vec3{max.x, max.y, min.z}, to_uv, texId);
|
|
||||||
v.emplace_back(glm::vec3{min.x, max.y, min.z}, glm::vec2{to_uv.x, from_uv.y}, texId);
|
|
||||||
break;
|
|
||||||
case EnumFace::South:
|
|
||||||
v.emplace_back(glm::vec3{min.x, min.y, max.z}, from_uv, texId);
|
|
||||||
v.emplace_back(glm::vec3{max.x, min.y, max.z}, glm::vec2{from_uv.x, to_uv.y}, texId);
|
|
||||||
v.emplace_back(glm::vec3{max.x, max.y, max.z}, to_uv, texId);
|
|
||||||
v.emplace_back(glm::vec3{min.x, min.y, max.z}, from_uv, texId);
|
|
||||||
v.emplace_back(glm::vec3{max.x, max.y, max.z}, to_uv, texId);
|
|
||||||
v.emplace_back(glm::vec3{min.x, max.y, max.z}, glm::vec2{to_uv.x, from_uv.y}, texId);
|
|
||||||
break;
|
|
||||||
case EnumFace::West:
|
|
||||||
v.emplace_back(glm::vec3{min.x, min.y, min.z}, from_uv, texId);
|
|
||||||
v.emplace_back(glm::vec3{min.x, min.y, max.z}, glm::vec2{from_uv.x, to_uv.y}, texId);
|
|
||||||
v.emplace_back(glm::vec3{min.x, max.y, max.z}, to_uv, texId);
|
|
||||||
v.emplace_back(glm::vec3{min.x, min.y, min.z}, from_uv, texId);
|
|
||||||
v.emplace_back(glm::vec3{min.x, max.y, max.z}, to_uv, texId);
|
|
||||||
v.emplace_back(glm::vec3{min.x, max.y, min.z}, glm::vec2{to_uv.x, from_uv.y}, texId);
|
|
||||||
break;
|
|
||||||
case EnumFace::East:
|
case EnumFace::East:
|
||||||
v.emplace_back(glm::vec3{max.x, min.y, min.z}, from_uv, texId);
|
addQuad({x1, y0, z1}, {x1, y0, z0}, {x1, y1, z0}, {x1, y1, z1},
|
||||||
v.emplace_back(glm::vec3{max.x, min.y, max.z}, glm::vec2{from_uv.x, to_uv.y}, texId);
|
{u0, v0}, {u0, v1}, {u1, v1}, {u1, v0});
|
||||||
v.emplace_back(glm::vec3{max.x, max.y, max.z}, to_uv, texId);
|
break;
|
||||||
v.emplace_back(glm::vec3{max.x, min.y, min.z}, from_uv, texId);
|
case EnumFace::West:
|
||||||
v.emplace_back(glm::vec3{max.x, max.y, max.z}, to_uv, texId);
|
addQuad({x0, y0, z1}, {x0, y1, z1}, {x0, y1, z0}, {x0, y0, z0},
|
||||||
v.emplace_back(glm::vec3{max.x, max.y, min.z}, glm::vec2{to_uv.x, from_uv.y}, texId);
|
{u0, v0}, {u1, v0}, {u1, v1}, {u0, v1});
|
||||||
break;
|
break;
|
||||||
|
case EnumFace::South:
|
||||||
|
addQuad({x0, y0, z1}, {x1, y0, z1}, {x1, y1, z1}, {x0, y1, z1},
|
||||||
|
{u0, v0}, {u1, v0}, {u1, v1}, {u0, v1});
|
||||||
|
break;
|
||||||
|
case EnumFace::North:
|
||||||
|
addQuad({x0, y0, z0}, {x0, y1, z0}, {x1, y1, z0}, {x1, y0, z0},
|
||||||
|
{u0, v0}, {u0, v1}, {u1, v1}, {u1, v0});
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
MAKE_ERROR("EnumFace::None");
|
MAKE_ERROR("EnumFace::None");
|
||||||
}
|
}
|
||||||
@@ -223,6 +236,16 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!pm.SubModels.empty() && modelAssets) {
|
||||||
|
model.Depends.reserve(pm.SubModels.size());
|
||||||
|
for(const auto& sub : pm.SubModels) {
|
||||||
|
auto iter = modelKeyToId.find(sub.Domain + ':' + sub.Key);
|
||||||
|
if(iter == modelKeyToId.end())
|
||||||
|
continue;
|
||||||
|
model.Depends.emplace_back(iter->second, Transformations{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// struct Face {
|
// struct Face {
|
||||||
// int TintIndex = -1;
|
// int TintIndex = -1;
|
||||||
// int16_t Rotation = 0;
|
// int16_t Rotation = 0;
|
||||||
@@ -271,12 +294,16 @@ private:
|
|||||||
Logger LOG = "Client>ModelProvider";
|
Logger LOG = "Client>ModelProvider";
|
||||||
// Таблица моделей
|
// Таблица моделей
|
||||||
std::unordered_map<ResourceId, ModelObject> Models;
|
std::unordered_map<ResourceId, ModelObject> Models;
|
||||||
|
std::unordered_set<ResourceId> MissingModelsLogged;
|
||||||
uint64_t UniqId = 0;
|
uint64_t UniqId = 0;
|
||||||
|
|
||||||
Model getModel(ResourceId id, std::vector<ResourceId>& used) {
|
Model getModel(ResourceId id, std::vector<ResourceId>& used) {
|
||||||
auto iterModel = Models.find(id);
|
auto iterModel = Models.find(id);
|
||||||
if(iterModel == Models.end()) {
|
if(iterModel == Models.end()) {
|
||||||
// Нет такой модели, ну и хрен с ним
|
// Нет такой модели, ну и хрен с ним
|
||||||
|
if(MissingModelsLogged.insert(id).second) {
|
||||||
|
LOG.warn() << "Missing model id=" << id;
|
||||||
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -708,6 +735,15 @@ public:
|
|||||||
} else if(data.starts_with((const char8_t*) "{")) {
|
} else if(data.starts_with((const char8_t*) "{")) {
|
||||||
type = "InternalJson";
|
type = "InternalJson";
|
||||||
// nodestate в json формате
|
// nodestate в json формате
|
||||||
|
} else {
|
||||||
|
type = "InternalBinaryLegacy";
|
||||||
|
// Старый двоичный формат без заголовка "bn"
|
||||||
|
std::u8string patched;
|
||||||
|
patched.reserve(data.size() + 2);
|
||||||
|
patched.push_back(u8'b');
|
||||||
|
patched.push_back(u8'n');
|
||||||
|
patched.append(data);
|
||||||
|
nodestate = PreparedNodeState(patched);
|
||||||
}
|
}
|
||||||
} catch(const std::exception& exc) {
|
} catch(const std::exception& exc) {
|
||||||
LOG.warn() << "Не удалось распарсить nodestate " << type << ":\n\t" << exc.what();
|
LOG.warn() << "Не удалось распарсить nodestate " << type << ":\n\t" << exc.what();
|
||||||
@@ -715,6 +751,14 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
Nodestates.insert_or_assign(key, std::move(nodestate));
|
Nodestates.insert_or_assign(key, std::move(nodestate));
|
||||||
|
if(key < 64) {
|
||||||
|
auto iter = Nodestates.find(key);
|
||||||
|
if(iter != Nodestates.end()) {
|
||||||
|
LOG.debug() << "Nodestate loaded id=" << key
|
||||||
|
<< " routes=" << iter->second.Routes.size()
|
||||||
|
<< " models=" << iter->second.LocalToModel.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!changedModels.empty()) {
|
if(!changedModels.empty()) {
|
||||||
@@ -745,11 +789,26 @@ public:
|
|||||||
// states - Текущие значения состояний ноды
|
// states - Текущие значения состояний ноды
|
||||||
std::vector<std::vector<std::pair<float, std::unordered_map<EnumFace, std::vector<NodeVertexStatic>>>>> getModelsForNode(AssetsNodestate id, const std::vector<NodeStateInfo>& statesInfo, const std::unordered_map<std::string, int32_t>& states) {
|
std::vector<std::vector<std::pair<float, std::unordered_map<EnumFace, std::vector<NodeVertexStatic>>>>> getModelsForNode(AssetsNodestate id, const std::vector<NodeStateInfo>& statesInfo, const std::unordered_map<std::string, int32_t>& states) {
|
||||||
auto iterNodestate = Nodestates.find(id);
|
auto iterNodestate = Nodestates.find(id);
|
||||||
if(iterNodestate == Nodestates.end())
|
if(iterNodestate == Nodestates.end()) {
|
||||||
|
if(MissingNodestateLogged.insert(id).second) {
|
||||||
|
LOG.warn() << "Missing nodestate id=" << id;
|
||||||
|
}
|
||||||
return {};
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
PreparedNodeState& nodestate = iterNodestate->second;
|
PreparedNodeState& nodestate = iterNodestate->second;
|
||||||
std::vector<uint16_t> routes = nodestate.getModelsForState(statesInfo, states);
|
std::vector<uint16_t> routes = nodestate.getModelsForState(statesInfo, states);
|
||||||
|
if(routes.empty()) {
|
||||||
|
int32_t metaValue = 0;
|
||||||
|
if(auto iterMeta = states.find("meta"); iterMeta != states.end())
|
||||||
|
metaValue = iterMeta->second;
|
||||||
|
uint64_t key = (uint64_t(id) << 32) | (uint32_t(metaValue) & 0xffffffffu);
|
||||||
|
if(EmptyRouteLogged.insert(key).second) {
|
||||||
|
LOG.warn() << "No nodestate routes id=" << id
|
||||||
|
<< " meta=" << metaValue
|
||||||
|
<< " total_routes=" << nodestate.Routes.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
std::vector<std::vector<std::pair<float, std::unordered_map<EnumFace, std::vector<NodeVertexStatic>>>>> result;
|
std::vector<std::vector<std::pair<float, std::unordered_map<EnumFace, std::vector<NodeVertexStatic>>>>> result;
|
||||||
|
|
||||||
std::unordered_map<TexturePipeline, uint16_t> pipelineResolveCache;
|
std::unordered_map<TexturePipeline, uint16_t> pipelineResolveCache;
|
||||||
@@ -765,12 +824,12 @@ public:
|
|||||||
for(const Vertex& v : r) {
|
for(const Vertex& v : r) {
|
||||||
NodeVertexStatic vert;
|
NodeVertexStatic vert;
|
||||||
|
|
||||||
vert.FX = (v.Pos.x+0.5f)*64+224;
|
vert.FX = (v.Pos.x + 16.0f) * 2.0f + 224.0f;
|
||||||
vert.FY = (v.Pos.y+0.5f)*64+224;
|
vert.FY = (v.Pos.y + 16.0f) * 2.0f + 224.0f;
|
||||||
vert.FZ = (v.Pos.z+0.5f)*64+224;
|
vert.FZ = (v.Pos.z + 16.0f) * 2.0f + 224.0f;
|
||||||
|
|
||||||
vert.TU = std::clamp<int32_t>(v.UV.x * (1 << 16), 0, (1 << 16) - 1);
|
vert.TU = std::clamp<int32_t>(v.UV.x * (1 << 11), 0, (1 << 16) - 1);
|
||||||
vert.TV = std::clamp<int32_t>(v.UV.y * (1 << 16), 0, (1 << 16) - 1);
|
vert.TV = std::clamp<int32_t>(v.UV.y * (1 << 11), 0, (1 << 16) - 1);
|
||||||
|
|
||||||
const TexturePipeline& pipe = model.TextureMap[model.TextureKeys[v.TexId]];
|
const TexturePipeline& pipe = model.TextureMap[model.TextureKeys[v.TexId]];
|
||||||
if(auto iterPipe = pipelineResolveCache.find(pipe); iterPipe != pipelineResolveCache.end()) {
|
if(auto iterPipe = pipelineResolveCache.find(pipe); iterPipe != pipelineResolveCache.end()) {
|
||||||
@@ -836,11 +895,17 @@ public:
|
|||||||
return TP.getTextureId(pipe);
|
return TP.getTextureId(pipe);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool hasNodestate(AssetsNodestate id) const {
|
||||||
|
return Nodestates.contains(id);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Logger LOG = "Client>NodestateProvider";
|
Logger LOG = "Client>NodestateProvider";
|
||||||
ModelProvider& MP;
|
ModelProvider& MP;
|
||||||
TextureProvider& TP;
|
TextureProvider& TP;
|
||||||
std::unordered_map<AssetsNodestate, PreparedNodeState> Nodestates;
|
std::unordered_map<AssetsNodestate, PreparedNodeState> Nodestates;
|
||||||
|
std::unordered_set<AssetsNodestate> MissingNodestateLogged;
|
||||||
|
std::unordered_set<uint64_t> EmptyRouteLogged;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -997,6 +997,9 @@ PreparedNodeState::PreparedNodeState(const std::u8string_view data) {
|
|||||||
std::u8string PreparedNodeState::dump() const {
|
std::u8string PreparedNodeState::dump() const {
|
||||||
Net::Packet result;
|
Net::Packet result;
|
||||||
|
|
||||||
|
const char magic[] = "bn";
|
||||||
|
result.write(reinterpret_cast<const std::byte*>(magic), 2);
|
||||||
|
|
||||||
// ResourceToLocalId
|
// ResourceToLocalId
|
||||||
assert(LocalToModelKD.size() < (1 << 16));
|
assert(LocalToModelKD.size() < (1 << 16));
|
||||||
assert(LocalToModelKD.size() == LocalToModel.size());
|
assert(LocalToModelKD.size() == LocalToModel.size());
|
||||||
@@ -1357,6 +1360,7 @@ uint16_t PreparedNodeState::parseCondition(const std::string_view expression) {
|
|||||||
bin.rhs = *nodeId;
|
bin.rhs = *nodeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
node.v = bin;
|
||||||
Nodes.emplace_back(std::move(node));
|
Nodes.emplace_back(std::move(node));
|
||||||
assert(Nodes.size() < std::pow(2, 16)-64);
|
assert(Nodes.size() < std::pow(2, 16)-64);
|
||||||
leftToken = uint16_t(Nodes.size()-1);
|
leftToken = uint16_t(Nodes.size()-1);
|
||||||
@@ -1756,6 +1760,29 @@ PreparedModel::PreparedModel(const std::string_view modid, const js::object& pro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(boost::system::result<const js::value&> submodels_val = profile.try_at("sub_models")) {
|
||||||
|
const js::array& submodels = submodels_val->as_array();
|
||||||
|
SubModels.reserve(submodels.size());
|
||||||
|
|
||||||
|
for(const js::value& value : submodels) {
|
||||||
|
if(const auto model_key = value.try_as_string()) {
|
||||||
|
auto [domain, key] = parseDomainKey((std::string) *model_key, modid);
|
||||||
|
SubModels.push_back({std::move(domain), std::move(key), std::nullopt});
|
||||||
|
} else {
|
||||||
|
const js::object& obj = value.as_object();
|
||||||
|
const std::string model_key_str = (std::string) obj.at("model").as_string();
|
||||||
|
auto [domain, key] = parseDomainKey(model_key_str, modid);
|
||||||
|
|
||||||
|
std::optional<uint16_t> scene;
|
||||||
|
if(const auto scene_val = obj.try_at("scene")) {
|
||||||
|
scene = static_cast<uint16_t>(scene_val->to_number<int>());
|
||||||
|
}
|
||||||
|
|
||||||
|
SubModels.push_back({std::move(domain), std::move(key), scene});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(boost::system::result<const js::value&> subModels_val = profile.try_at("sub_models")) {
|
if(boost::system::result<const js::value&> subModels_val = profile.try_at("sub_models")) {
|
||||||
const js::array& subModels = subModels_val->as_array();
|
const js::array& subModels = subModels_val->as_array();
|
||||||
|
|
||||||
|
|||||||
@@ -713,13 +713,15 @@ struct PreparedNodeState {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
for(const auto& route : Routes)
|
||||||
|
lambda(route.first);
|
||||||
|
|
||||||
std::sort(variables.begin(), variables.end());
|
std::sort(variables.begin(), variables.end());
|
||||||
auto eraseIter = std::unique(variables.begin(), variables.end());
|
auto eraseIter = std::unique(variables.begin(), variables.end());
|
||||||
variables.erase(eraseIter, variables.end());
|
variables.erase(eraseIter, variables.end());
|
||||||
|
|
||||||
bool ok = false;
|
|
||||||
|
|
||||||
for(const std::string_view key : variables) {
|
for(const std::string_view key : variables) {
|
||||||
|
bool ok = false;
|
||||||
if(size_t pos = key.find(':'); pos != std::string::npos) {
|
if(size_t pos = key.find(':'); pos != std::string::npos) {
|
||||||
std::string_view state, value;
|
std::string_view state, value;
|
||||||
state = key.substr(0, pos);
|
state = key.substr(0, pos);
|
||||||
|
|||||||
@@ -77,7 +77,8 @@ enum struct L2System : uint8_t {
|
|||||||
Disconnect,
|
Disconnect,
|
||||||
Test_CAM_PYR_POS,
|
Test_CAM_PYR_POS,
|
||||||
BlockChange,
|
BlockChange,
|
||||||
ResourceRequest
|
ResourceRequest,
|
||||||
|
ReloadMods
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -221,11 +221,18 @@ std::tuple<ResourceId, std::optional<AssetsManager::DataEntry>&> AssetsManager::
|
|||||||
|
|
||||||
for(size_t index = 0; index < table.size(); index++) {
|
for(size_t index = 0; index < table.size(); index++) {
|
||||||
auto& entry = *table[index];
|
auto& entry = *table[index];
|
||||||
|
if(index == 0 && entry.Empty.test(0)) {
|
||||||
|
entry.Empty.reset(0);
|
||||||
|
}
|
||||||
|
|
||||||
if(entry.IsFull)
|
if(entry.IsFull)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
uint32_t pos = entry.Empty._Find_first();
|
uint32_t pos = entry.Empty._Find_first();
|
||||||
|
if(pos == entry.Empty.size()) {
|
||||||
|
entry.IsFull = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
entry.Empty.reset(pos);
|
entry.Empty.reset(pos);
|
||||||
|
|
||||||
if(entry.Empty._Find_next(pos) == entry.Empty.size())
|
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;
|
id = index*TableEntry<DataEntry>::ChunkSize + pos;
|
||||||
data = &entry.Entries[pos];
|
data = &entry.Entries[pos];
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!data) {
|
if(!data) {
|
||||||
table.emplace_back(std::make_unique<TableEntry<DataEntry>>());
|
table.emplace_back(std::make_unique<TableEntry<DataEntry>>());
|
||||||
id = (table.size()-1)*TableEntry<DataEntry>::ChunkSize;
|
auto& entry = *table.back();
|
||||||
data = &table.back()->Entries[0];
|
if(table.size() == 1 && entry.Empty.test(0)) {
|
||||||
table.back()->Empty.reset(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)
|
if(type == EnumAssets::Nodestate)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ namespace LV::Server {
|
|||||||
ContentManager::ContentManager(AssetsManager &am)
|
ContentManager::ContentManager(AssetsManager &am)
|
||||||
: AM(am)
|
: AM(am)
|
||||||
{
|
{
|
||||||
|
std::fill(std::begin(NextId), std::end(NextId), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentManager::~ContentManager() = default;
|
ContentManager::~ContentManager() = default;
|
||||||
@@ -111,6 +111,31 @@ void ContentManager::unRegisterModifier(EnumDefContent type, const std::string&
|
|||||||
ProfileChanges[(int) type].push_back(id);
|
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() {
|
ContentManager::Out_buildEndProfiles ContentManager::buildEndProfiles() {
|
||||||
Out_buildEndProfiles result;
|
Out_buildEndProfiles 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];
|
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 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 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 {
|
struct Out_buildEndProfiles {
|
||||||
std::vector<ResourceId> ChangedProfiles[(int) EnumDefContent::MAX_ENUM];
|
std::vector<ResourceId> ChangedProfiles[(int) EnumDefContent::MAX_ENUM];
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <optional>
|
||||||
#include <sol/forward.hpp>
|
#include <sol/forward.hpp>
|
||||||
#include <sol/protected_function_result.hpp>
|
#include <sol/protected_function_result.hpp>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
@@ -1107,7 +1108,7 @@ coro<> GameServer::pushSocketGameProtocol(tcp::socket socket, const std::string
|
|||||||
co_await Net::AsyncSocket::write<uint8_t>(socket, 0);
|
co_await Net::AsyncSocket::write<uint8_t>(socket, 0);
|
||||||
|
|
||||||
External.NewConnectedPlayers.lock_write()
|
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();
|
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);
|
Content.CM.registerBase(EnumDefContent::World, "test", "devel_world", t);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1453,11 +1454,11 @@ void GameServer::init(fs::path worldPath) {
|
|||||||
|
|
||||||
// TODO: регистрация контента из mod/content/*
|
// TODO: регистрация контента из mod/content/*
|
||||||
|
|
||||||
Content.CM.buildEndProfiles();
|
|
||||||
|
|
||||||
pushEvent("preInit");
|
pushEvent("preInit");
|
||||||
pushEvent("highPreInit");
|
pushEvent("highPreInit");
|
||||||
|
|
||||||
|
Content.CM.buildEndProfiles();
|
||||||
|
|
||||||
|
|
||||||
LOG.info() << "Инициализация";
|
LOG.info() << "Инициализация";
|
||||||
initLua();
|
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() {
|
void GameServer::stepConnections() {
|
||||||
// Подключить новых игроков
|
// Подключить новых игроков
|
||||||
if(!External.NewConnectedPlayers.no_lock_readable().empty()) {
|
if(!External.NewConnectedPlayers.no_lock_readable().empty()) {
|
||||||
@@ -1715,9 +1723,42 @@ void GameServer::stepConnections() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GameServer::stepModInitializations() {
|
void GameServer::stepModInitializations() {
|
||||||
|
if(ModsReloadRequested.exchange(false)) {
|
||||||
|
reloadMods();
|
||||||
|
}
|
||||||
BackingChunkPressure.endWithResults();
|
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_Out GameServer::stepDatabaseSync() {
|
||||||
IWorldSaveBackend::TickSyncInfo_In toDB;
|
IWorldSaveBackend::TickSyncInfo_In toDB;
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#include <Common/Net.hpp>
|
#include <Common/Net.hpp>
|
||||||
#include <Common/Lockable.hpp>
|
#include <Common/Lockable.hpp>
|
||||||
|
#include <atomic>
|
||||||
#include <boost/asio/any_io_executor.hpp>
|
#include <boost/asio/any_io_executor.hpp>
|
||||||
#include <boost/asio/io_context.hpp>
|
#include <boost/asio/io_context.hpp>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
@@ -58,6 +59,7 @@ class GameServer : public AsyncObject {
|
|||||||
|
|
||||||
bool IsAlive = true, IsGoingShutdown = false;
|
bool IsAlive = true, IsGoingShutdown = false;
|
||||||
std::string ShutdownReason;
|
std::string ShutdownReason;
|
||||||
|
std::atomic<bool> ModsReloadRequested = false;
|
||||||
static constexpr float
|
static constexpr float
|
||||||
PerTickDuration = 1/30.f, // Минимальная и стартовая длина такта
|
PerTickDuration = 1/30.f, // Минимальная и стартовая длина такта
|
||||||
PerTickAdjustment = 1/60.f; // Подгонка длительности такта в случае провисаний
|
PerTickAdjustment = 1/60.f; // Подгонка длительности такта в случае провисаний
|
||||||
@@ -283,6 +285,7 @@ public:
|
|||||||
void waitShutdown() {
|
void waitShutdown() {
|
||||||
UseLock.wait_no_use();
|
UseLock.wait_no_use();
|
||||||
}
|
}
|
||||||
|
void requestModsReload();
|
||||||
|
|
||||||
// Подключение tcp сокета
|
// Подключение tcp сокета
|
||||||
coro<> pushSocketConnect(tcp::socket socket);
|
coro<> pushSocketConnect(tcp::socket socket);
|
||||||
@@ -315,6 +318,7 @@ private:
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
void stepModInitializations();
|
void stepModInitializations();
|
||||||
|
void reloadMods();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Пересчёт зон видимости игроков, если необходимо
|
Пересчёт зон видимости игроков, если необходимо
|
||||||
|
|||||||
@@ -3,8 +3,10 @@
|
|||||||
#include "Common/Abstract.hpp"
|
#include "Common/Abstract.hpp"
|
||||||
#include "Common/Net.hpp"
|
#include "Common/Net.hpp"
|
||||||
#include "Server/Abstract.hpp"
|
#include "Server/Abstract.hpp"
|
||||||
|
#include "Server/GameServer.hpp"
|
||||||
#include "Server/World.hpp"
|
#include "Server/World.hpp"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <atomic>
|
||||||
#include <boost/asio/error.hpp>
|
#include <boost/asio/error.hpp>
|
||||||
#include <boost/system/system_error.hpp>
|
#include <boost/system/system_error.hpp>
|
||||||
#include <exception>
|
#include <exception>
|
||||||
@@ -13,6 +15,23 @@
|
|||||||
|
|
||||||
namespace LV::Server {
|
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() {
|
RemoteClient::~RemoteClient() {
|
||||||
shutdown(EnumDisconnect::ByInterface, "~RemoteClient()");
|
shutdown(EnumDisconnect::ByInterface, "~RemoteClient()");
|
||||||
if(Socket.isAlive()) {
|
if(Socket.isAlive()) {
|
||||||
@@ -487,9 +506,13 @@ ResourceRequest RemoteClient::pushPreparedPackets() {
|
|||||||
nextRequest = std::move(lock->NextRequest);
|
nextRequest = std::move(lock->NextRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(AssetsInWork.AssetsPacket.size()) {
|
if(!AssetsInWork.AssetsPackets.empty()) {
|
||||||
toSend.push_back(std::move(AssetsInWork.AssetsPacket));
|
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;
|
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)
|
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;
|
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) {
|
for(auto& [type, resId, domain, key, resource] : resources) {
|
||||||
auto hash = resource.hash();
|
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) {
|
if(it == AssetsInWork.OnClient.end() || *it != hash) {
|
||||||
AssetsInWork.OnClient.insert(it, hash);
|
AssetsInWork.OnClient.insert(it, hash);
|
||||||
AssetsInWork.ToSend.emplace_back(type, domain, key, resId, resource, 0);
|
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 {
|
} else {
|
||||||
LOG.warn() << "Клиент повторно запросил имеющийся у него ресурс";
|
LOG.warn() << "Клиент повторно запросил имеющийся у него ресурс";
|
||||||
}
|
}
|
||||||
@@ -720,6 +760,7 @@ coro<> RemoteClient::rP_System(Net::AsyncSocket &sock) {
|
|||||||
}
|
}
|
||||||
case ToServer::L2System::ResourceRequest:
|
case ToServer::L2System::ResourceRequest:
|
||||||
{
|
{
|
||||||
|
static std::atomic<uint32_t> debugRequestLogCount = 0;
|
||||||
uint16_t count = co_await sock.read<uint16_t>();
|
uint16_t count = co_await sock.read<uint16_t>();
|
||||||
std::vector<Hash_t> hashes;
|
std::vector<Hash_t> hashes;
|
||||||
hashes.reserve(count);
|
hashes.reserve(count);
|
||||||
@@ -733,6 +774,29 @@ coro<> RemoteClient::rP_System(Net::AsyncSocket &sock) {
|
|||||||
auto lock = NetworkAndResource.lock();
|
auto lock = NetworkAndResource.lock();
|
||||||
lock->NextRequest.Hashes.append_range(hashes);
|
lock->NextRequest.Hashes.append_range(hashes);
|
||||||
lock->ClientRequested.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;
|
co_return;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -818,24 +882,59 @@ void RemoteClient::onUpdate() {
|
|||||||
// Отправка ресурсов
|
// Отправка ресурсов
|
||||||
if(!AssetsInWork.ToSend.empty()) {
|
if(!AssetsInWork.ToSend.empty()) {
|
||||||
auto& toSend = AssetsInWork.ToSend;
|
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);
|
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;
|
Net::Packet& p = AssetsInWork.AssetsPacket;
|
||||||
|
|
||||||
|
auto flushAssetsPacket = [&]() {
|
||||||
|
if(p.size() == 0)
|
||||||
|
return;
|
||||||
|
AssetsInWork.AssetsPackets.push_back(std::move(p));
|
||||||
|
};
|
||||||
|
|
||||||
bool hasFullSended = false;
|
bool hasFullSended = false;
|
||||||
|
|
||||||
for(auto& [type, domain, key, id, res, sended] : toSend) {
|
for(auto& [type, domain, key, id, res, sended] : toSend) {
|
||||||
if(sended == 0) {
|
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
|
p << (uint8_t) ToClient::L1::Resource
|
||||||
<< (uint8_t) ToClient::L2Resource::InitResSend
|
<< (uint8_t) ToClient::L2Resource::InitResSend
|
||||||
<< uint32_t(res.size());
|
<< uint32_t(res.size());
|
||||||
p.write((const std::byte*) res.hash().data(), 32);
|
p.write((const std::byte*) res.hash().data(), 32);
|
||||||
p << uint32_t(id) << uint8_t(type) << domain << key;
|
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);
|
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
|
p << (uint8_t) ToClient::L1::Resource
|
||||||
<< (uint8_t) ToClient::L2Resource::ChunkSend;
|
<< (uint8_t) ToClient::L2Resource::ChunkSend;
|
||||||
p.write((const std::byte*) res.hash().data(), 32);
|
p.write((const std::byte*) res.hash().data(), 32);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
namespace LV::Server {
|
namespace LV::Server {
|
||||||
|
|
||||||
class World;
|
class World;
|
||||||
|
class GameServer;
|
||||||
|
|
||||||
template<typename ServerKey, typename ClientKey, std::enable_if_t<sizeof(ServerKey) >= sizeof(ClientKey), int> = 0>
|
template<typename ServerKey, typename ClientKey, std::enable_if_t<sizeof(ServerKey) >= sizeof(ClientKey), int> = 0>
|
||||||
class CSChunkedMapper {
|
class CSChunkedMapper {
|
||||||
@@ -316,6 +317,7 @@ class RemoteClient {
|
|||||||
// Тип, домен, ключ, идентификатор, ресурс, количество отправленных байт
|
// Тип, домен, ключ, идентификатор, ресурс, количество отправленных байт
|
||||||
std::vector<std::tuple<EnumAssets, std::string, std::string, ResourceId, Resource, size_t>> ToSend;
|
std::vector<std::tuple<EnumAssets, std::string, std::string, ResourceId, Resource, size_t>> ToSend;
|
||||||
// Пакет с ресурсами
|
// Пакет с ресурсами
|
||||||
|
std::vector<Net::Packet> AssetsPackets;
|
||||||
Net::Packet AssetsPacket;
|
Net::Packet AssetsPacket;
|
||||||
} AssetsInWork;
|
} AssetsInWork;
|
||||||
|
|
||||||
@@ -336,8 +338,8 @@ public:
|
|||||||
std::queue<Pos::GlobalNode> Build, Break;
|
std::queue<Pos::GlobalNode> Build, Break;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
RemoteClient(asio::io_context &ioc, tcp::socket socket, const std::string 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)
|
: LOG("RemoteClient " + username), Socket(ioc, std::move(socket)), Username(username), Server(server)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
~RemoteClient();
|
~RemoteClient();
|
||||||
@@ -434,6 +436,7 @@ public:
|
|||||||
void onUpdate();
|
void onUpdate();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
GameServer* Server = nullptr;
|
||||||
void protocolError();
|
void protocolError();
|
||||||
coro<> readPacket(Net::AsyncSocket &sock);
|
coro<> readPacket(Net::AsyncSocket &sock);
|
||||||
coro<> rP_System(Net::AsyncSocket &sock);
|
coro<> rP_System(Net::AsyncSocket &sock);
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ void main() {
|
|||||||
Frame = atlasColor(Fragment.Texture, Fragment.UV);
|
Frame = atlasColor(Fragment.Texture, Fragment.UV);
|
||||||
Frame.xyz *= max(0.2f, dot(Fragment.Normal, normalize(vec3(0.5, 1, 0.8))));
|
Frame.xyz *= max(0.2f, dot(Fragment.Normal, normalize(vec3(0.5, 1, 0.8))));
|
||||||
// Frame = vec4(blendOverlay(vec3(Frame), vec3(Fragment.GeoPos/64.f)), Frame.w);
|
// Frame = vec4(blendOverlay(vec3(Frame), vec3(Fragment.GeoPos/64.f)), Frame.w);
|
||||||
|
|
||||||
if(Frame.w == 0)
|
if(Frame.w == 0)
|
||||||
discard;
|
discard;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user