*
This commit is contained in:
@@ -64,10 +64,9 @@ public:
|
||||
class IRenderSession {
|
||||
public:
|
||||
// Подгрузка двоичных ресурсов
|
||||
virtual void onBinaryResourceAdd(std::unordered_map<EnumBinResource, std::unordered_map<ResourceId_t, BinaryResource>>) = 0;
|
||||
virtual void onBinaryResourceLost(std::unordered_map<EnumBinResource, std::vector<ResourceId_t>>) = 0;
|
||||
virtual void onBinaryResourceAdd(std::vector<Hash_t>) = 0;
|
||||
|
||||
virtual void onContentDefinesAdd(std::unordered_map<EnumDefContent, std::unordered_map<ResourceId_t, std::u8string>>) = 0;
|
||||
virtual void onContentDefinesAdd(std::unordered_map<EnumDefContent, std::vector<ResourceId_t>>) = 0;
|
||||
virtual void onContentDefinesLost(std::unordered_map<EnumDefContent, std::vector<ResourceId_t>>) = 0;
|
||||
|
||||
// Сообщаем об изменившихся чанках
|
||||
@@ -90,10 +89,6 @@ struct DefVoxelInfo {
|
||||
|
||||
};
|
||||
|
||||
struct DefNodeInfo {
|
||||
|
||||
};
|
||||
|
||||
struct DefWorldInfo {
|
||||
|
||||
};
|
||||
@@ -142,10 +137,24 @@ struct DefItemInfo {
|
||||
|
||||
/* Интерфейс обработчика сессии с сервером */
|
||||
class IServerSession {
|
||||
struct ArrayHasher {
|
||||
std::size_t operator()(const Hash_t& a) const {
|
||||
std::size_t h = 0;
|
||||
for (auto e : a)
|
||||
h ^= std::hash<int>{}(e) + 0x9e3779b9 + (h << 6) + (h >> 2);
|
||||
|
||||
return h;
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
struct {
|
||||
std::unordered_map<Hash_t, BinaryResource, ArrayHasher> Resources;
|
||||
} Binary;
|
||||
|
||||
struct {
|
||||
std::unordered_map<DefVoxelId_t, DefVoxelInfo> DefVoxel;
|
||||
std::unordered_map<DefNodeId_t, DefNodeInfo> DefNode;
|
||||
std::unordered_map<DefNodeId_t, DefNode_t> DefNode;
|
||||
std::unordered_map<DefWorldId_t, DefWorldInfo> DefWorld;
|
||||
std::unordered_map<DefPortalId_t, DefPortalInfo> DefPortal;
|
||||
std::unordered_map<DefEntityId_t, DefEntityInfo> DefEntity;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "Common/Abstract.hpp"
|
||||
#include "Common/Net.hpp"
|
||||
#include "TOSLib.hpp"
|
||||
#include "glm/ext/quaternion_geometric.hpp"
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <boost/asio/deadline_timer.hpp>
|
||||
#include <boost/asio/this_coro.hpp>
|
||||
@@ -24,8 +25,8 @@ struct PP_Content_ChunkVoxels : public ParsedPacket {
|
||||
Pos::GlobalChunk Pos;
|
||||
std::vector<VoxelCube> Cubes;
|
||||
|
||||
PP_Content_ChunkVoxels(ToClient::L1 l1, uint8_t l2, WorldId_t id, Pos::GlobalChunk pos, std::vector<VoxelCube> &&cubes)
|
||||
: ParsedPacket(l1, l2), Id(id), Pos(pos), Cubes(std::move(cubes))
|
||||
PP_Content_ChunkVoxels(WorldId_t id, Pos::GlobalChunk pos, std::vector<VoxelCube> &&cubes)
|
||||
: ParsedPacket(ToClient::L1::Content, (uint8_t) ToClient::L2Content::ChunkVoxels), Id(id), Pos(pos), Cubes(std::move(cubes))
|
||||
{}
|
||||
};
|
||||
|
||||
@@ -34,8 +35,8 @@ struct PP_Content_ChunkNodes : public ParsedPacket {
|
||||
Pos::GlobalChunk Pos;
|
||||
std::array<Node, 16*16*16> Nodes;
|
||||
|
||||
PP_Content_ChunkNodes(ToClient::L1 l1, uint8_t l2, WorldId_t id, Pos::GlobalChunk pos)
|
||||
: ParsedPacket(l1, l2), Id(id), Pos(pos)
|
||||
PP_Content_ChunkNodes(WorldId_t id, Pos::GlobalChunk pos)
|
||||
: ParsedPacket(ToClient::L1::Content, (uint8_t) ToClient::L2Content::ChunkNodes), Id(id), Pos(pos)
|
||||
{
|
||||
}
|
||||
};
|
||||
@@ -44,8 +45,36 @@ struct PP_Content_RegionRemove : public ParsedPacket {
|
||||
WorldId_t Id;
|
||||
Pos::GlobalRegion Pos;
|
||||
|
||||
PP_Content_RegionRemove(ToClient::L1 l1, uint8_t l2, WorldId_t id, Pos::GlobalRegion pos)
|
||||
: ParsedPacket(l1, l2), Id(id), Pos(pos)
|
||||
PP_Content_RegionRemove(WorldId_t id, Pos::GlobalRegion pos)
|
||||
: ParsedPacket(ToClient::L1::Content, (uint8_t) ToClient::L2Content::RemoveRegion), Id(id), Pos(pos)
|
||||
{}
|
||||
};
|
||||
|
||||
struct PP_Definition_FreeNode : public ParsedPacket {
|
||||
DefNodeId_t Id;
|
||||
|
||||
PP_Definition_FreeNode(DefNodeId_t id)
|
||||
: ParsedPacket(ToClient::L1::Definition, (uint8_t) ToClient::L2Definition::Node),
|
||||
Id(id)
|
||||
{}
|
||||
};
|
||||
|
||||
struct PP_Definition_Node : public ParsedPacket {
|
||||
DefNodeId_t Id;
|
||||
DefNode_t Def;
|
||||
|
||||
PP_Definition_Node(DefNodeId_t id, DefNode_t def)
|
||||
: ParsedPacket(ToClient::L1::Definition, (uint8_t) ToClient::L2Definition::Node),
|
||||
Id(id), Def(def)
|
||||
{}
|
||||
};
|
||||
|
||||
struct PP_Resource_InitResSend : public ParsedPacket {
|
||||
Hash_t Hash;
|
||||
BinaryResource Resource;
|
||||
|
||||
PP_Resource_InitResSend(Hash_t hash, BinaryResource res)
|
||||
: ParsedPacket(ToClient::L1::Resource, (uint8_t) ToClient::L2Resource::InitResSend), Hash(hash), Resource(res)
|
||||
{}
|
||||
};
|
||||
|
||||
@@ -283,11 +312,32 @@ void ServerSession::atFreeDrawTime(GlobalTime gTime, float dTime) {
|
||||
|
||||
{
|
||||
std::unordered_map<WorldId_t, std::tuple<std::unordered_set<Pos::GlobalChunk>, std::unordered_set<Pos::GlobalRegion>>> changeOrAddList_removeList;
|
||||
std::unordered_map<EnumDefContent, std::vector<ResourceId_t>> onContentDefinesAdd;
|
||||
std::unordered_map<EnumDefContent, std::vector<ResourceId_t>> onContentDefinesLost;
|
||||
|
||||
// Пакеты
|
||||
ParsedPacket *pack;
|
||||
while(NetInputPackets.pop(pack)) {
|
||||
if(pack->Level1 == ToClient::L1::Content) {
|
||||
if(pack->Level1 == ToClient::L1::Definition) {
|
||||
ToClient::L2Resource l2 = ToClient::L2Resource(pack->Level2);
|
||||
if(l2 == ToClient::L2Resource::InitResSend) {
|
||||
PP_Resource_InitResSend &p = *dynamic_cast<PP_Resource_InitResSend*>(pack);
|
||||
|
||||
}
|
||||
|
||||
} else if(pack->Level1 == ToClient::L1::Definition) {
|
||||
ToClient::L2Definition l2 = ToClient::L2Definition(pack->Level2);
|
||||
if(l2 == ToClient::L2Definition::Node) {
|
||||
PP_Definition_Node &p = *dynamic_cast<PP_Definition_Node*>(pack);
|
||||
Registry.DefNode[p.Id] = p.Def;
|
||||
onContentDefinesAdd[EnumDefContent::Node].push_back(p.Id);
|
||||
} else if(l2 == ToClient::L2Definition::FreeNode) {
|
||||
PP_Definition_FreeNode &p = *dynamic_cast<PP_Definition_FreeNode*>(pack);
|
||||
onContentDefinesLost[EnumDefContent::Node].push_back(p.Id);
|
||||
}
|
||||
|
||||
|
||||
} else if(pack->Level1 == ToClient::L1::Content) {
|
||||
ToClient::L2Content l2 = ToClient::L2Content(pack->Level2);
|
||||
if(l2 == ToClient::L2Content::ChunkVoxels) {
|
||||
PP_Content_ChunkVoxels &p = *dynamic_cast<PP_Content_ChunkVoxels*>(pack);
|
||||
@@ -339,6 +389,14 @@ void ServerSession::atFreeDrawTime(GlobalTime gTime, float dTime) {
|
||||
|
||||
RS->onChunksChange(pair.first, std::get<0>(pair.second), std::get<1>(pair.second));
|
||||
}
|
||||
|
||||
if(!onContentDefinesAdd.empty()) {
|
||||
RS->onContentDefinesAdd(std::move(onContentDefinesAdd));
|
||||
}
|
||||
|
||||
if(!onContentDefinesLost.empty()) {
|
||||
RS->onContentDefinesLost(std::move(onContentDefinesLost));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,7 +406,10 @@ void ServerSession::atFreeDrawTime(GlobalTime gTime, float dTime) {
|
||||
|
||||
glm::quat quat =
|
||||
glm::angleAxis(PYR.x-deltaTime*PYR_Offset.x, glm::vec3(1.f, 0.f, 0.f))
|
||||
* glm::angleAxis(PYR.y-deltaTime*PYR_Offset.y, glm::vec3(0.f, -1.f, 0.f));
|
||||
*
|
||||
glm::angleAxis(PYR.y-deltaTime*PYR_Offset.y, glm::vec3(0.f, -1.f, 0.f));
|
||||
|
||||
quat = glm::normalize(quat);
|
||||
|
||||
if(RS)
|
||||
RS->setCameraPos(0, Pos, quat);
|
||||
@@ -360,7 +421,7 @@ void ServerSession::atFreeDrawTime(GlobalTime gTime, float dTime) {
|
||||
LastSendPYR_POS = gTime;
|
||||
Net::Packet packet;
|
||||
ToServer::PacketQuat q;
|
||||
q.fromQuat(quat);
|
||||
q.fromQuat(glm::inverse(quat));
|
||||
|
||||
packet << (uint8_t) ToServer::L1::System
|
||||
<< (uint8_t) ToServer::L2System::Test_CAM_PYR_POS
|
||||
@@ -452,7 +513,7 @@ coro<> ServerSession::rP_Resource(Net::AsyncSocket &sock) {
|
||||
|
||||
switch((ToClient::L2Resource) second) {
|
||||
case ToClient::L2Resource::Texture:
|
||||
|
||||
|
||||
co_return;
|
||||
case ToClient::L2Resource::FreeTexture:
|
||||
|
||||
@@ -470,13 +531,27 @@ coro<> ServerSession::rP_Resource(Net::AsyncSocket &sock) {
|
||||
|
||||
co_return;
|
||||
case ToClient::L2Resource::InitResSend:
|
||||
{
|
||||
uint32_t size = co_await sock.read<uint32_t>();
|
||||
Hash_t hash;
|
||||
co_await sock.read((std::byte*) hash.data(), hash.size());
|
||||
|
||||
uint32_t chunkSize = co_await sock.read<uint32_t>();
|
||||
std::u8string data(size, '\0');
|
||||
|
||||
co_await sock.read((std::byte*) data.data(), data.size());
|
||||
|
||||
PP_Resource_InitResSend *packet = new PP_Resource_InitResSend(
|
||||
hash,
|
||||
std::make_shared<std::u8string>(std::move(data))
|
||||
);
|
||||
|
||||
while(!NetInputPackets.push(packet));
|
||||
|
||||
co_return;
|
||||
}
|
||||
case ToClient::L2Resource::ChunkSend:
|
||||
|
||||
co_return;
|
||||
case ToClient::L2Resource::SendCanceled:
|
||||
|
||||
co_return;
|
||||
default:
|
||||
protocolError();
|
||||
@@ -508,11 +583,38 @@ coro<> ServerSession::rP_Definition(Net::AsyncSocket &sock) {
|
||||
co_return;
|
||||
}
|
||||
case ToClient::L2Definition::Node:
|
||||
{
|
||||
DefNodeId_t id;
|
||||
DefNode_t def;
|
||||
id = co_await sock.read<DefNodeId_t>();
|
||||
def.DrawType = (DefNode_t::EnumDrawType) co_await sock.read<uint8_t>();
|
||||
for(int iter = 0; iter < 6; iter++) {
|
||||
auto &pl = def.Texs[iter].Pipeline;
|
||||
pl.resize(co_await sock.read<uint16_t>());
|
||||
co_await sock.read((std::byte*) pl.data(), pl.size());
|
||||
}
|
||||
|
||||
PP_Definition_Node *packet = new PP_Definition_Node(
|
||||
id,
|
||||
def
|
||||
);
|
||||
|
||||
while(!NetInputPackets.push(packet));
|
||||
|
||||
co_return;
|
||||
}
|
||||
case ToClient::L2Definition::FreeNode:
|
||||
{
|
||||
DefNodeId_t id = co_await sock.read<DefNodeId_t>();
|
||||
|
||||
PP_Definition_FreeNode *packet = new PP_Definition_FreeNode(
|
||||
id
|
||||
);
|
||||
|
||||
while(!NetInputPackets.push(packet));
|
||||
|
||||
co_return;
|
||||
}
|
||||
case ToClient::L2Definition::Portal:
|
||||
|
||||
co_return;
|
||||
@@ -564,8 +666,6 @@ coro<> ServerSession::rP_Content(Net::AsyncSocket &sock) {
|
||||
co_await sock.read((std::byte*) compressed.data(), compressedSize);
|
||||
|
||||
PP_Content_ChunkVoxels *packet = new PP_Content_ChunkVoxels(
|
||||
ToClient::L1::Content,
|
||||
(uint8_t) ToClient::L2Content::ChunkVoxels,
|
||||
wcId,
|
||||
pos,
|
||||
unCompressVoxels(compressed) // TODO: вынести в отдельный поток
|
||||
@@ -588,8 +688,6 @@ coro<> ServerSession::rP_Content(Net::AsyncSocket &sock) {
|
||||
co_await sock.read((std::byte*) compressed.data(), compressedSize);
|
||||
|
||||
PP_Content_ChunkNodes *packet = new PP_Content_ChunkNodes(
|
||||
ToClient::L1::Content,
|
||||
(uint8_t) ToClient::L2Content::ChunkNodes,
|
||||
wcId,
|
||||
pos
|
||||
);
|
||||
@@ -609,8 +707,6 @@ coro<> ServerSession::rP_Content(Net::AsyncSocket &sock) {
|
||||
pos.unpack(co_await sock.read<Pos::GlobalRegion::Pack>());
|
||||
|
||||
PP_Content_RegionRemove *packet = new PP_Content_RegionRemove(
|
||||
ToClient::L1::Content,
|
||||
(uint8_t) ToClient::L2Content::RemoveRegion,
|
||||
wcId,
|
||||
pos
|
||||
);
|
||||
|
||||
@@ -32,7 +32,7 @@ struct VoxelVertexPoint {
|
||||
|
||||
struct NodeVertexStatic {
|
||||
uint32_t
|
||||
FX : 9, FY : 9, FZ : 9, // Позиция 15 -120 ~ 240 360 15 / 16
|
||||
FX : 9, FY : 9, FZ : 9, // Позиция -15 -120 ~ 240 360 +15 / 16
|
||||
N1 : 4, // Не занято
|
||||
LS : 1, // Масштаб карты освещения (1м/16 или 1м)
|
||||
Tex : 18, // Текстура
|
||||
|
||||
@@ -304,9 +304,9 @@ public:
|
||||
WritePos = 0;
|
||||
|
||||
while(!postponed.empty()) {
|
||||
Task& task = TasksWait.front();
|
||||
Task& task = postponed.front();
|
||||
pushData(std::move(task.Data), task.PoolId, task.BlockId);
|
||||
TasksWait.pop();
|
||||
postponed.pop();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2695,7 +2695,7 @@ Buffer& Buffer::operator=(Buffer &&obj) {
|
||||
std::swap(Memory, obj.Memory);
|
||||
std::swap(Size, obj.Size);
|
||||
obj.Instance = nullptr;
|
||||
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#include "VulkanRenderSession.hpp"
|
||||
#include "Client/Abstract.hpp"
|
||||
#include "Client/Vulkan/Abstract.hpp"
|
||||
#include "Client/Vulkan/Vulkan.hpp"
|
||||
#include "Common/Abstract.hpp"
|
||||
#include "TOSLib.hpp"
|
||||
#include "assets.hpp"
|
||||
#include "glm/ext/matrix_transform.hpp"
|
||||
#include "glm/ext/scalar_constants.hpp"
|
||||
#include "glm/matrix.hpp"
|
||||
#include "glm/trigonometric.hpp"
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
@@ -211,7 +213,8 @@ void VulkanRenderSession::init(Vulkan *instance) {
|
||||
"willow_wood.png",
|
||||
"tropical_rainforest_wood.png",
|
||||
"xnether_blue_wood.png",
|
||||
"xnether_purple_wood.png"
|
||||
"xnether_purple_wood.png",
|
||||
"frame.png"
|
||||
}) {
|
||||
ByteBuffer image = VK::loadPNG(getResource(std::string("textures/") + path)->makeStream().Stream, width, height, hasAlpha);
|
||||
uint16_t texId = VKCTX->MainTest.atlasAddTexture(width, height);
|
||||
@@ -229,35 +232,66 @@ void VulkanRenderSession::init(Vulkan *instance) {
|
||||
|
||||
{
|
||||
NodeVertexStatic *array = (NodeVertexStatic*) VKCTX->TestQuad.mapMemory();
|
||||
array[0] = {135, 135, 135-64, 0, 0, 0, 0, 0, 0};
|
||||
array[1] = {135+16, 135, 135-64, 0, 0, 0, 0, 65535, 0};
|
||||
array[2] = {135+16, 135+16, 135-64, 0, 0, 0, 0, 65535, 65535};
|
||||
array[3] = {135, 135, 135-64, 0, 0, 0, 0, 0, 0};
|
||||
array[4] = {135+16, 135+16, 135-64, 0, 0, 0, 0, 65535, 65535};
|
||||
array[5] = {135, 135+16, 135-64, 0, 0, 0, 0, 0, 65535};
|
||||
array[0] = {135, 135, 135, 0, 0, 0, 0, 65535, 0};
|
||||
array[1] = {135, 135+16, 135, 0, 0, 0, 0, 0, 65535};
|
||||
array[2] = {135+16, 135+16, 135, 0, 0, 0, 0, 0, 65535};
|
||||
array[3] = {135, 135, 135, 0, 0, 0, 0, 65535, 0};
|
||||
array[4] = {135+16, 135+16, 135, 0, 0, 0, 0, 0, 65535};
|
||||
array[5] = {135+16, 135, 135, 0, 0, 0, 0, 0, 0};
|
||||
|
||||
array[6] = {135, 135, 135+16, 0, 0, 0, 0, 0, 0};
|
||||
array[7] = {135+16, 135, 135+16, 0, 0, 0, 0, 65535, 0};
|
||||
array[8] = {135+16, 135+16, 135+16, 0, 0, 0, 0, 65535, 65535};
|
||||
array[9] = {135, 135, 135+16, 0, 0, 0, 0, 0, 0};
|
||||
array[10] = {135+16, 135+16, 135+16, 0, 0, 0, 0, 65535, 65535};
|
||||
array[11] = {135, 135+16, 135+16, 0, 0, 0, 0, 0, 65535};
|
||||
|
||||
array[6] = {135, 135, 135-64-16, 0, 0, 0, 0, 0, 0};
|
||||
array[7] = {135, 135, 135-64, 0, 0, 0, 0, 65535, 0};
|
||||
array[8] = {135, 135+16, 135-64, 0, 0, 0, 0, 65535, 65535};
|
||||
array[9] = {135, 135, 135-64-16, 0, 0, 0, 0, 0, 0};
|
||||
array[10] = {135, 135+16, 135-64, 0, 0, 0, 0, 65535, 65535};
|
||||
array[11] = {135, 135+16, 135-64-16, 0, 0, 0, 0, 0, 65535};
|
||||
array[12] = {135, 135, 135, 0, 0, 0, 0, 0, 0};
|
||||
array[13] = {135, 135, 135+16, 0, 0, 0, 0, 65535, 0};
|
||||
array[14] = {135, 135+16, 135+16, 0, 0, 0, 0, 65535, 65535};
|
||||
array[15] = {135, 135, 135, 0, 0, 0, 0, 0, 0};
|
||||
array[16] = {135, 135+16, 135+16, 0, 0, 0, 0, 65535, 65535};
|
||||
array[17] = {135, 135+16, 135, 0, 0, 0, 0, 0, 65535};
|
||||
|
||||
array[18] = {135+16, 135, 135+16, 0, 0, 0, 0, 0, 0};
|
||||
array[19] = {135+16, 135, 135, 0, 0, 0, 0, 65535, 0};
|
||||
array[20] = {135+16, 135+16, 135, 0, 0, 0, 0, 65535, 65535};
|
||||
array[21] = {135+16, 135, 135+16, 0, 0, 0, 0, 0, 0};
|
||||
array[22] = {135+16, 135+16, 135, 0, 0, 0, 0, 65535, 65535};
|
||||
array[23] = {135+16, 135+16, 135+16, 0, 0, 0, 0, 0, 65535};
|
||||
|
||||
array[12] = {135, 135, 135-64, 0, 0, 0, 0, 0, 0};
|
||||
array[13] = {135+16, 135, 135-64, 0, 0, 0, 0, 65535, 0};
|
||||
array[14] = {135+16, 135, 135-64-16, 0, 0, 0, 0, 65535, 65535};
|
||||
array[15] = {135, 135, 135-64, 0, 0, 0, 0, 0, 0};
|
||||
array[16] = {135+16, 135, 135-64-16, 0, 0, 0, 0, 65535, 65535};
|
||||
array[17] = {135, 135, 135-64-16, 0, 0, 0, 0, 0, 65535};
|
||||
array[24] = {135, 135, 135, 0, 0, 0, 0, 0, 0};
|
||||
array[25] = {135+16, 135, 135, 0, 0, 0, 0, 65535, 0};
|
||||
array[26] = {135+16, 135, 135+16, 0, 0, 0, 0, 65535, 65535};
|
||||
array[27] = {135, 135, 135, 0, 0, 0, 0, 0, 0};
|
||||
array[28] = {135+16, 135, 135+16, 0, 0, 0, 0, 65535, 65535};
|
||||
array[29] = {135, 135, 135+16, 0, 0, 0, 0, 0, 65535};
|
||||
|
||||
for(int iter = 0; iter < 18; iter++) {
|
||||
array[18+iter] = array[iter];
|
||||
array[18+iter].FZ -= 32;
|
||||
array[30] = {135, 135+16, 135+16, 0, 0, 0, 0, 0, 0};
|
||||
array[31] = {135+16, 135+16, 135+16, 0, 0, 0, 0, 65535, 0};
|
||||
array[32] = {135+16, 135+16, 135, 0, 0, 0, 0, 65535, 65535};
|
||||
array[33] = {135, 135+16, 135+16, 0, 0, 0, 0, 0, 0};
|
||||
array[34] = {135+16, 135+16, 135, 0, 0, 0, 0, 65535, 65535};
|
||||
array[35] = {135, 135+16, 135, 0, 0, 0, 0, 0, 65535};
|
||||
|
||||
for(int iter = 0; iter < 36; iter++) {
|
||||
array[iter].Tex = 6;
|
||||
if(array[iter].FX == 135)
|
||||
array[iter].FX--;
|
||||
else
|
||||
array[iter].FX++;
|
||||
|
||||
if(array[iter].FY == 135)
|
||||
array[iter].FY--;
|
||||
else
|
||||
array[iter].FY++;
|
||||
|
||||
if(array[iter].FZ == 135)
|
||||
array[iter].FZ--;
|
||||
else
|
||||
array[iter].FZ++;
|
||||
}
|
||||
|
||||
|
||||
VKCTX->TestQuad.unMapMemory();
|
||||
}
|
||||
|
||||
@@ -627,15 +661,11 @@ void VulkanRenderSession::init(Vulkan *instance) {
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanRenderSession::onBinaryResourceAdd(std::unordered_map<EnumBinResource, std::unordered_map<ResourceId_t, BinaryResource>>) {
|
||||
void VulkanRenderSession::onBinaryResourceAdd(std::vector<Hash_t>) {
|
||||
|
||||
}
|
||||
|
||||
void VulkanRenderSession::onBinaryResourceLost(std::unordered_map<EnumBinResource, std::vector<ResourceId_t>>) {
|
||||
|
||||
}
|
||||
|
||||
void VulkanRenderSession::onContentDefinesAdd(std::unordered_map<EnumDefContent, std::unordered_map<ResourceId_t, std::u8string>>) {
|
||||
void VulkanRenderSession::onContentDefinesAdd(std::unordered_map<EnumDefContent, std::vector<ResourceId_t>>) {
|
||||
|
||||
}
|
||||
|
||||
@@ -643,7 +673,6 @@ void VulkanRenderSession::onContentDefinesLost(std::unordered_map<EnumDefContent
|
||||
|
||||
}
|
||||
|
||||
int changed = 0;
|
||||
void VulkanRenderSession::onChunksChange(WorldId_t worldId, const std::unordered_set<Pos::GlobalChunk>& changeOrAddList, const std::unordered_set<Pos::GlobalRegion>& remove) {
|
||||
auto &table = External.ChunkVoxelMesh[worldId];
|
||||
|
||||
@@ -668,7 +697,6 @@ void VulkanRenderSession::onChunksChange(WorldId_t worldId, const std::unordered
|
||||
if(vertexs2.empty()) {
|
||||
VKCTX->VertexPool_Nodes.dropVertexs(std::get<1>(buffers));
|
||||
} else {
|
||||
changed++;
|
||||
auto &nodes = std::get<1>(buffers);
|
||||
VKCTX->VertexPool_Nodes.relocate(nodes, std::move(vertexs2));
|
||||
}
|
||||
@@ -678,8 +706,6 @@ void VulkanRenderSession::onChunksChange(WorldId_t worldId, const std::unordered
|
||||
if(iter != table.end())
|
||||
table.erase(iter);
|
||||
}
|
||||
|
||||
TOS::Logger("Vul").debug() << "Обработано " << changed;
|
||||
}
|
||||
|
||||
for(Pos::GlobalRegion pos : remove) {
|
||||
@@ -717,7 +743,7 @@ void VulkanRenderSession::beforeDraw() {
|
||||
void VulkanRenderSession::drawWorld(GlobalTime gTime, float dTime, VkCommandBuffer drawCmd) {
|
||||
{
|
||||
X64Offset = Pos & ~((1 << Pos::Object_t::BS_Bit << 4 << 2)-1);
|
||||
X64Offset_f = glm::vec3(X64Offset) / float(Pos::Object_t::BS);
|
||||
X64Offset_f = glm::vec3(X64Offset >> Pos::Object_t::BS_Bit);
|
||||
X64Delta = glm::vec3(Pos-X64Offset) / float(Pos::Object_t::BS);
|
||||
}
|
||||
|
||||
@@ -817,7 +843,21 @@ void VulkanRenderSession::drawWorld(GlobalTime gTime, float dTime, VkCommandBuff
|
||||
Delta += dTime;
|
||||
|
||||
PCO.Model = glm::mat4(1);
|
||||
PCO.Model = glm::translate(PCO.Model, -X64Offset_f);
|
||||
//PCO.Model = glm::translate(PCO.Model, -X64Offset_f);
|
||||
// glm::quat quat = glm::inverse(Quat);
|
||||
|
||||
{
|
||||
|
||||
// auto *srv = (class ServerSession*) ServerSession;
|
||||
|
||||
glm::vec4 v = glm::mat4(glm::inverse(Quat))*glm::vec4(0, 0, -6, 1);
|
||||
|
||||
Pos::GlobalNode pos = (Pos::GlobalNode) (glm::vec3) v;
|
||||
|
||||
pos += (Pos-X64Offset) >> Pos::Object_t::BS_Bit;
|
||||
PCO.Model = glm::translate(PCO.Model, glm::vec3(pos));
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
glm::mat4 proj = glm::perspective<float>(glm::radians(75.f), float(VkInst->Screen.Width)/float(VkInst->Screen.Height), 0.5, std::pow(2, 17));
|
||||
@@ -832,7 +872,7 @@ void VulkanRenderSession::drawWorld(GlobalTime gTime, float dTime, VkCommandBuff
|
||||
// Смещаем мир относительно позиции игрока, чтобы игрок в пространстве рендера оказался в нулевых координатах
|
||||
view = glm::translate(view, -X64Delta);
|
||||
// Поворачиваем мир обратно взгляду игрока, чтобы его взгляд стал по направлению оси -z
|
||||
view = glm::mat4(-Quat)*view;
|
||||
view = glm::mat4(Quat)*view;
|
||||
|
||||
// Сначала применяется матрица вида, потом проекции
|
||||
PCO.ProjView = proj*view;
|
||||
|
||||
@@ -141,9 +141,8 @@ public:
|
||||
assert(serverSession);
|
||||
}
|
||||
|
||||
virtual void onBinaryResourceAdd(std::unordered_map<EnumBinResource, std::unordered_map<ResourceId_t, BinaryResource>>) override;
|
||||
virtual void onBinaryResourceLost(std::unordered_map<EnumBinResource, std::vector<ResourceId_t>>) override;
|
||||
virtual void onContentDefinesAdd(std::unordered_map<EnumDefContent, std::unordered_map<ResourceId_t, std::u8string>>) override;
|
||||
virtual void onBinaryResourceAdd(std::vector<Hash_t>) override;
|
||||
virtual void onContentDefinesAdd(std::unordered_map<EnumDefContent, std::vector<ResourceId_t>>) override;
|
||||
virtual void onContentDefinesLost(std::unordered_map<EnumDefContent, std::vector<ResourceId_t>>) override;
|
||||
virtual void onChunksChange(WorldId_t worldId, const std::unordered_set<Pos::GlobalChunk>& changeOrAddList, const std::unordered_set<Pos::GlobalRegion>& remove) override;
|
||||
virtual void setCameraPos(WorldId_t worldId, Pos::Object pos, glm::quat quat) override;
|
||||
|
||||
@@ -485,6 +485,28 @@ void unCompressNodes(const std::u8string& compressed, Node* ptr);
|
||||
std::u8string compressLinear(const std::u8string& data);
|
||||
std::u8string unCompressLinear(const std::u8string& data);
|
||||
|
||||
enum struct TexturePipelineCMD : uint8_t {
|
||||
Texture, // Указание текстуры
|
||||
Combine, // Комбинирование
|
||||
|
||||
};
|
||||
|
||||
struct TexturePipeline {
|
||||
std::vector<BinTextureId_t> BinTextures;
|
||||
std::u8string Pipeline;
|
||||
};
|
||||
|
||||
struct DefNode_t {
|
||||
enum struct EnumDrawType : uint8_t {
|
||||
NoDraw, // Не рисуется
|
||||
Simple, // Простая нода с текстурами на каждой стороне
|
||||
} DrawType = EnumDrawType::Simple;
|
||||
|
||||
TexturePipeline Texs[6];
|
||||
};
|
||||
|
||||
using Hash_t = std::array<uint8_t, 32>;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -32,14 +32,14 @@ struct PacketQuat {
|
||||
value |= uint64_t(w & 0x3ff) << 30;
|
||||
|
||||
for(int iter = 0; iter < 5; iter++)
|
||||
Data[iter] = (value >> (iter * 8)) & 0xff;
|
||||
Data[iter] = (value >> (iter*8)) & 0xff;
|
||||
}
|
||||
|
||||
glm::quat toQuat() const {
|
||||
uint64_t value = 0;
|
||||
|
||||
for(int iter = 0; iter < 5; iter++)
|
||||
value |= (Data[iter] >> (iter*10)) & 0x3ff;
|
||||
value |= uint64_t(Data[iter]) << (iter*8);
|
||||
|
||||
uint16_t
|
||||
x = value & 0x3ff,
|
||||
@@ -52,7 +52,7 @@ struct PacketQuat {
|
||||
float fz = (float(z)/0x3ff)*2-1;
|
||||
float fw = (float(w)/0x3ff)*2-1;
|
||||
|
||||
return glm::quat(fx, fy, fz, fw);
|
||||
return glm::quat(fw, fx, fy, fz);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -144,19 +144,10 @@ enum struct L2System : uint8_t {
|
||||
};
|
||||
|
||||
enum struct L2Resource : uint8_t {
|
||||
Texture,
|
||||
FreeTexture,
|
||||
Animation,
|
||||
FreeAnimation,
|
||||
Sound,
|
||||
FreeSound,
|
||||
Model,
|
||||
FreeModel,
|
||||
Font,
|
||||
FreeFont,
|
||||
Bind, // Привязка идентификаторов ресурсов к хешам
|
||||
Lost,
|
||||
InitResSend = 253,
|
||||
ChunkSend,
|
||||
SendCanceled
|
||||
ChunkSend
|
||||
};
|
||||
|
||||
enum struct L2Definition : uint8_t {
|
||||
|
||||
@@ -3,19 +3,13 @@
|
||||
#include <cstdint>
|
||||
#include <Common/Abstract.hpp>
|
||||
#include <Common/Collide.hpp>
|
||||
#include <boost/uuid/detail/sha1.hpp>
|
||||
#include <sha2.hpp>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
|
||||
namespace LV::Server {
|
||||
|
||||
struct TexturePipeline {
|
||||
std::vector<BinTextureId_t> BinTextures;
|
||||
std::u8string Pipeline;
|
||||
};
|
||||
|
||||
|
||||
// В одном регионе может быть максимум 2^16 сущностей. Клиенту адресуются сущности в формате <мир>+<позиция региона>+<uint16_t>
|
||||
// И если сущность перешла из одного региона в другой, идентификатор сущности на стороне клиента сохраняется
|
||||
using RegionEntityId_t = uint16_t;
|
||||
@@ -39,15 +33,13 @@ using DefGeneratorId_t = ResourceId_t;
|
||||
*/
|
||||
|
||||
struct ResourceFile {
|
||||
using Hash_t = boost::uuids::detail::sha1::digest_type;
|
||||
using Hash_t = sha2::sha256_hash; // boost::uuids::detail::sha1::digest_type;
|
||||
|
||||
Hash_t Hash;
|
||||
std::vector<std::byte> Data;
|
||||
|
||||
void calcHash() {
|
||||
boost::uuids::detail::sha1 hash;
|
||||
hash.process_bytes(Data.data(), Data.size());
|
||||
hash.get_digest(Hash);
|
||||
Hash = sha2::sha256((const uint8_t*) Data.data(), Data.size());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -11,9 +11,8 @@ namespace LV::Server {
|
||||
|
||||
|
||||
|
||||
BinaryResourceManager::BinaryResourceManager(asio::io_context &ioc,
|
||||
std::shared_ptr<ResourceFile> zeroResource)
|
||||
: AsyncObject(ioc), ZeroResource(std::move(zeroResource))
|
||||
BinaryResourceManager::BinaryResourceManager(asio::io_context &ioc)
|
||||
: AsyncObject(ioc)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -43,15 +42,13 @@ void BinaryResourceManager::update(float dtime) {
|
||||
|
||||
auto lock = UpdatedResources.lock_write();
|
||||
for(ResourceId_t resId : *lock) {
|
||||
std::shared_ptr<ResourceFile> &objRes = PreparedInformation[resId];
|
||||
if(objRes)
|
||||
auto iterPI = PreparedInformation.find(resId);
|
||||
if(iterPI != PreparedInformation.end())
|
||||
continue;
|
||||
|
||||
auto iter = ResourcesInfo.find(resId);
|
||||
if(iter == ResourcesInfo.end()) {
|
||||
objRes = ZeroResource;
|
||||
} else {
|
||||
objRes = iter->second->Loaded;
|
||||
auto iterRI = ResourcesInfo.find(resId);
|
||||
if(iterRI != ResourcesInfo.end()) {
|
||||
PreparedInformation[resId] = iterRI->second->Loaded;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -81,7 +78,6 @@ ResourceId_t BinaryResourceManager::getResource_Assets(std::string path) {
|
||||
std::shared_ptr<Resource> &res = ResourcesInfo[resId];
|
||||
if(!res) {
|
||||
res = std::make_shared<Resource>();
|
||||
res->Loaded = ZeroResource;
|
||||
|
||||
auto iter = Domains.find("domain");
|
||||
if(iter == Domains.end()) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "Common/Abstract.hpp"
|
||||
#include "Common/Lockable.hpp"
|
||||
#include "Server/RemoteClient.hpp"
|
||||
#include <functional>
|
||||
@@ -10,12 +11,24 @@
|
||||
#include <vector>
|
||||
#include <Common/Async.hpp>
|
||||
#include "Abstract.hpp"
|
||||
#include "TOSLib.hpp"
|
||||
|
||||
|
||||
namespace LV::Server {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
/*
|
||||
Может прийти множество запросов на один не загруженный ресурс
|
||||
|
||||
Чтение происходит отдельным потоком, переконвертацию пока предлагаю в realtime.
|
||||
Хэш вычисляется после чтения и может быть иным чем при прошлом чтении (ресурс изменили наживую)
|
||||
тогда обычным оповещениям клиентам дойдёт новая версия
|
||||
|
||||
Подержать какое-то время ресурс в памяти
|
||||
|
||||
*/
|
||||
|
||||
class BinaryResourceManager : public AsyncObject {
|
||||
public:
|
||||
|
||||
@@ -26,49 +39,54 @@ private:
|
||||
// Источник
|
||||
std::string Uri;
|
||||
bool IsLoading = false;
|
||||
|
||||
std::string LastError;
|
||||
};
|
||||
|
||||
struct UriParse {
|
||||
std::string Orig, Protocol, Path;
|
||||
};
|
||||
|
||||
// Последовательная регистрация ресурсов
|
||||
BinTextureId_t NextIdTexture = 0, NextIdAnimation = 0, NextIdModel = 0,
|
||||
NextIdSound = 0, NextIdFont = 0;
|
||||
|
||||
// Ресурсы - кешированные в оперативную память или в процессе загрузки
|
||||
std::map<BinTextureId_t, std::shared_ptr<Resource>>
|
||||
|
||||
// Нулевой ресурс
|
||||
std::shared_ptr<ResourceFile> ZeroResource;
|
||||
// Домены поиска ресурсов
|
||||
std::unordered_map<std::string, fs::path> Domains;
|
||||
// Известные ресурсы
|
||||
std::map<std::string, ResourceId_t> KnownResource;
|
||||
std::map<ResourceId_t, std::shared_ptr<Resource>> ResourcesInfo;
|
||||
// Последовательная регистрация ресурсов
|
||||
ResourceId_t NextId = 1;
|
||||
// Накапливаем идентификаторы готовых ресурсов
|
||||
Lockable<std::vector<ResourceId_t>> UpdatedResources;
|
||||
// Сюда
|
||||
TOS::SpinlockObject<std::vector<ResourceId_t>> UpdatedResources;
|
||||
// Подготовленая таблица оповещения об изменениях ресурсов
|
||||
// Должна забираться сервером и отчищаться
|
||||
std::unordered_map<ResourceId_t, std::shared_ptr<ResourceFile>> PreparedInformation;
|
||||
|
||||
public:
|
||||
// Если ресурс будет обновлён или загружен будет вызвано onResourceUpdate
|
||||
BinaryResourceManager(asio::io_context &ioc, std::shared_ptr<ResourceFile> zeroResource);
|
||||
BinaryResourceManager(asio::io_context &ioc);
|
||||
virtual ~BinaryResourceManager();
|
||||
|
||||
// Перепроверка изменений ресурсов
|
||||
void recheckResources();
|
||||
// Домен мода -> путь к папке с ресурсами
|
||||
void setAssetsDomain(std::unordered_map<std::string, fs::path> &&domains) { Domains = std::move(domains); }
|
||||
// Идентификатор ресурса по его uri
|
||||
ResourceId_t mapUriToId(const std::string &uri);
|
||||
void recheckResources(std::vector<fs::path> assets /* Пути до активных папок assets */);
|
||||
// Выдаёт или назначает идентификатор для ресурса
|
||||
BinTextureId_t getTexture (const std::string& uri);
|
||||
BinAnimationId_t getAnimation(const std::string& uri);
|
||||
BinModelId_t getModel (const std::string& uri);
|
||||
BinSoundId_t getSound (const std::string& uri);
|
||||
BinFontId_t getFont (const std::string& uri);
|
||||
|
||||
// Запросить ресурсы через onResourceUpdate
|
||||
void needResourceResponse(const std::vector<ResourceId_t> &resources);
|
||||
// Серверный такт
|
||||
void update(float dtime);
|
||||
bool hasPreparedInformation() { return !PreparedInformation.empty(); }
|
||||
|
||||
void needResourceResponse(const ResourceRequest &&resources);
|
||||
// Получение обновлений или оповещений ресурсов
|
||||
std::unordered_map<ResourceId_t, std::shared_ptr<ResourceFile>> takePreparedInformation() {
|
||||
return std::move(PreparedInformation);
|
||||
}
|
||||
|
||||
// Серверный такт
|
||||
void update(float dtime);
|
||||
|
||||
protected:
|
||||
UriParse parseUri(const std::string &uri);
|
||||
ResourceId_t getResource_Assets(std::string path);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "RemoteClient.hpp"
|
||||
#include "Server/Abstract.hpp"
|
||||
#include "World.hpp"
|
||||
#include "glm/ext/quaternion_geometric.hpp"
|
||||
#include <algorithm>
|
||||
|
||||
|
||||
@@ -114,8 +115,10 @@ void ContentEventController::onUpdate() {
|
||||
uint8_t action = lock->front();
|
||||
lock->pop();
|
||||
|
||||
Pos::GlobalNode pos = (Pos::GlobalNode) (glm::vec3) (glm::mat4(Remote->CameraQuat.toQuat())*glm::vec4(0, 0, -1, 1));
|
||||
pos = Pos.ObjectPos >> Pos::Object_t::BS_Bit;
|
||||
glm::quat q = Remote->CameraQuat.toQuat();
|
||||
glm::vec4 v = glm::mat4(q)*glm::vec4(0, 0, -6, 1);
|
||||
Pos::GlobalNode pos = (Pos::GlobalNode) (glm::vec3) v;
|
||||
pos += Pos.ObjectPos >> Pos::Object_t::BS_Bit;
|
||||
|
||||
if(action == 0) {
|
||||
// Break
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include <array>
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <glm/geometric.hpp>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
@@ -15,11 +17,17 @@
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include "SaveBackends/Filesystem.hpp"
|
||||
#include "Server/SaveBackend.hpp"
|
||||
#include "Server/World.hpp"
|
||||
#include "TOSLib.hpp"
|
||||
#include "glm/gtc/noise.hpp"
|
||||
#include <fstream>
|
||||
|
||||
|
||||
namespace js = boost::json;
|
||||
|
||||
|
||||
namespace LV::Server {
|
||||
|
||||
@@ -27,20 +35,21 @@ GameServer::GameServer(asio::io_context &ioc, fs::path worldPath)
|
||||
: AsyncObject(ioc),
|
||||
Content(ioc, nullptr, nullptr, nullptr, nullptr, nullptr)
|
||||
{
|
||||
BackingChunkPressure.Threads.resize(4);
|
||||
BackingNoiseGenerator.Threads.resize(4);
|
||||
BackingAsyncLua.Threads.resize(4);
|
||||
|
||||
init(worldPath);
|
||||
|
||||
BackingChunkPressure.Threads.resize(4);
|
||||
BackingChunkPressure.Worlds = &Expanse.Worlds;
|
||||
for(size_t iter = 0; iter < BackingChunkPressure.Threads.size(); iter++) {
|
||||
BackingChunkPressure.Threads[iter] = std::thread(&BackingChunkPressure_t::run, &BackingChunkPressure, iter);
|
||||
}
|
||||
|
||||
BackingNoiseGenerator.Threads.resize(4);
|
||||
for(size_t iter = 0; iter < BackingNoiseGenerator.Threads.size(); iter++) {
|
||||
BackingNoiseGenerator.Threads[iter] = std::thread(&BackingNoiseGenerator_t::run, &BackingNoiseGenerator, iter);
|
||||
}
|
||||
|
||||
BackingAsyncLua.Threads.resize(4);
|
||||
for(size_t iter = 0; iter < BackingAsyncLua.Threads.size(); iter++) {
|
||||
BackingAsyncLua.Threads[iter] = std::thread(&BackingAsyncLua_t::run, &BackingAsyncLua, iter);
|
||||
}
|
||||
@@ -84,6 +93,9 @@ void GameServer::BackingChunkPressure_t::run(int id) {
|
||||
iteration = Iteration;
|
||||
}
|
||||
|
||||
assert(RunCollect > 0);
|
||||
assert(RunCompress > 0);
|
||||
|
||||
// Сбор данных
|
||||
size_t pullSize = Threads.size();
|
||||
size_t counter = 0;
|
||||
@@ -671,7 +683,7 @@ coro<> GameServer::pushSocketGameProtocol(tcp::socket socket, const std::string
|
||||
if(count > 262144)
|
||||
MAKE_ERROR("Не поддерживаемое количество ресурсов в кеше у клиента");
|
||||
|
||||
std::vector<HASH> clientCache;
|
||||
std::vector<Hash_t> clientCache;
|
||||
clientCache.resize(count);
|
||||
co_await Net::AsyncSocket::read(socket, (std::byte*) clientCache.data(), count*32);
|
||||
std::sort(clientCache.begin(), clientCache.end());
|
||||
@@ -682,6 +694,149 @@ coro<> GameServer::pushSocketGameProtocol(tcp::socket socket, const std::string
|
||||
}
|
||||
}
|
||||
|
||||
TexturePipeline GameServer::buildTexturePipeline(const std::string& pl) {
|
||||
/*
|
||||
^ объединение текстур, вторая поверх первой.
|
||||
При наложении текстуры будут автоматически увеличины до размера
|
||||
самой большой текстуры из участвующих. По умолчанию ближайший соседний
|
||||
default:dirt.png^our_tech:machine.png
|
||||
|
||||
Текстурные команды описываются в [] <- предоставляет текстуру.
|
||||
Разделитель пробелом
|
||||
default:dirt.png^[create 2 2 r ffaabbcc]
|
||||
|
||||
Если перед командой будет использован $, то первым аргументом будет
|
||||
предыдущая текстура, если это поддерживает команда
|
||||
default:dirt.png$[resize 16 16] или [resize default:dirt.png 16 16]
|
||||
|
||||
Группировка ()
|
||||
default:empty^(default:dirt.png^our_tech:machine.png)
|
||||
*/
|
||||
|
||||
std::map<std::string, BinTextureId_t> stbt;
|
||||
std::unordered_set<BinTextureId_t> btis;
|
||||
std::string alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
|
||||
// Парсер группы. Возвращает позицию на которой закончил и скомпилированный код
|
||||
std::move_only_function<std::tuple<size_t, std::u8string>(size_t pos)> parse_obj;
|
||||
std::move_only_function<std::tuple<size_t, std::u8string>(size_t pos, std::u8string maybe)> parse_cmd;
|
||||
|
||||
parse_cmd = [&](size_t pos, std::u8string maybe) -> std::tuple<size_t, std::u8string> {
|
||||
std::string cmd_name;
|
||||
std::vector<std::u8string> args;
|
||||
size_t startPos = pos;
|
||||
|
||||
for(pos++; pos < pl.size(); pos++) {
|
||||
if(pl[pos] == ']') {
|
||||
// Команда завершилась
|
||||
// return {pos+1, cmd_name};
|
||||
} else if(pl[pos] == ' ') {
|
||||
// Аргументы
|
||||
// Здесь нужно получить либо кастомные значения, либо объект
|
||||
auto [next_pos, subcmd] = parse_obj(pos+1);
|
||||
args.push_back(subcmd);
|
||||
if(next_pos == pl.size())
|
||||
MAKE_ERROR("Ожидался конец команды объявленной на " << startPos << ", наткнулись на конец потока");
|
||||
|
||||
pos = next_pos-1;
|
||||
} else if(alpha.find(pl[pos]) == std::string::npos) {
|
||||
MAKE_ERROR("Ошибка в имени команды");
|
||||
} else {
|
||||
cmd_name += pl[pos];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
parse_obj = [&](size_t pos) -> std::pair<size_t, std::u8string> {
|
||||
std::u8string out;
|
||||
|
||||
for(; pos < pl.size(); pos++) {
|
||||
if(pl[pos] == '[') {
|
||||
// Начало команды
|
||||
if(!out.empty()) {
|
||||
MAKE_ERROR("Отсутствует связь между текстурой и текущей командой " << pos);
|
||||
}
|
||||
|
||||
// out.push_back(TexturePipelineCMD::Combine);
|
||||
auto [next_size, subcmd] = parse_cmd(pos+1, {});
|
||||
pos = next_size-1;
|
||||
out = subcmd;
|
||||
} else if(pl[pos] == '^') {
|
||||
// Объединение
|
||||
if(out.empty()) {
|
||||
MAKE_ERROR("Отсутствует текстура для комбинирования " << pos);
|
||||
|
||||
auto [next_pos, subcmd] = parse_obj(pos+1);
|
||||
std::u8string cmd;
|
||||
cmd.push_back(uint8_t(TexturePipelineCMD::Combine));
|
||||
cmd.insert(cmd.end(), out.begin(), out.end());
|
||||
cmd.insert(cmd.end(), subcmd.begin(), subcmd.end());
|
||||
|
||||
return {next_pos, cmd};
|
||||
}
|
||||
} else if(pl[pos] == '$') {
|
||||
// Готовый набор команд будет использован как аргумент
|
||||
pos++;
|
||||
if(pos >= pl.size() || pl[pos] != '[')
|
||||
MAKE_ERROR("Ожидалось объявление команды " << pos);
|
||||
auto [next_pos, subcmd] = parse_cmd(pos, out);
|
||||
pos = next_pos-1;
|
||||
out = subcmd;
|
||||
} else if(pl[pos] == '(') {
|
||||
if(!out.empty()) {
|
||||
MAKE_ERROR("Начато определение группы после текстуры, вероятно пропущен знак объединения ^ " << pos);
|
||||
}
|
||||
|
||||
// Начало группы
|
||||
auto [next_pos, subcmd] = parse_obj(pos+1);
|
||||
pos = next_pos-1;
|
||||
out = subcmd;
|
||||
} else if(pl[pos] == ')') {
|
||||
return {pos+1, out};
|
||||
} else {
|
||||
// Это текстура, нужно её имя
|
||||
if(!out.empty())
|
||||
MAKE_ERROR("Отсутствует связь между текстурой и текущим объявлением текстуры " << pos);
|
||||
|
||||
out.push_back(uint8_t(TexturePipelineCMD::Texture));
|
||||
std::string texture_name;
|
||||
for(; pos < pl.size(); pos++) {
|
||||
if(pl[pos] == '^' || pl[pos] == ')' || pl[pos] == ']')
|
||||
break;
|
||||
else if(pl[pos] != '.' && pl[pos] != ':' && alpha.find_first_of(pl[pos]) != std::string::npos)
|
||||
MAKE_ERROR("Недействительные символы в объявлении текстуры " << pos);
|
||||
else
|
||||
texture_name += pl[pos];
|
||||
}
|
||||
|
||||
BinTextureId_t id = stbt[texture_name];
|
||||
btis.insert(id);
|
||||
|
||||
for(int iter = 0; iter < 4; iter++)
|
||||
out.push_back((id >> (iter * 8)) & 0xff);
|
||||
|
||||
if(pos < pl.size())
|
||||
pos--;
|
||||
}
|
||||
}
|
||||
|
||||
return {pos, out};
|
||||
};
|
||||
|
||||
auto [pos, cmd] = parse_obj(0);
|
||||
|
||||
if(pos < pl.size()) {
|
||||
MAKE_ERROR("Неожиданное продолжение " << pos);
|
||||
}
|
||||
|
||||
return {std::vector<BinTextureId_t>(btis.begin(), btis.end()), cmd};
|
||||
}
|
||||
|
||||
std::string GameServer::deBuildTexturePipeline(const TexturePipeline& pipeline) {
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
void GameServer::init(fs::path worldPath) {
|
||||
Expanse.Worlds[0] = std::make_unique<World>(0);
|
||||
|
||||
@@ -778,6 +933,60 @@ void GameServer::run() {
|
||||
LOG.info() << "Сервер завершил работу";
|
||||
}
|
||||
|
||||
std::vector<GameServer::ModInfo> GameServer::readModDataPath(const fs::path& modsDir) {
|
||||
if(!fs::exists(modsDir))
|
||||
return {};
|
||||
|
||||
std::vector<GameServer::ModInfo> infos;
|
||||
|
||||
try {
|
||||
fs::directory_iterator begin(modsDir), end;
|
||||
for(; begin != end; begin++) {
|
||||
if(!begin->is_directory())
|
||||
continue;
|
||||
|
||||
fs::path mod_conf = begin->path() / "mod.json";
|
||||
if(!fs::exists(mod_conf)) {
|
||||
LOG.debug() << "Директория в папке с модами не содержит файл mod.json: " << begin->path().filename();
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
std::ifstream fd(mod_conf);
|
||||
js::object obj = js::parse(fd).as_object();
|
||||
|
||||
GameServer::ModInfo info;
|
||||
|
||||
info.Id = obj.at("Id").as_string();
|
||||
info.Title = obj.contains("Title") ? obj["Title"].as_string() : "";
|
||||
info.Description = obj.contains("Description") ? obj["Description"].as_string() : "";
|
||||
|
||||
if(obj.contains("Dependencies")) {
|
||||
js::array arr = obj["Dependencies"].as_array();
|
||||
for(auto& iter : arr) {
|
||||
info.Dependencies.push_back((std::string) iter.as_string());
|
||||
}
|
||||
}
|
||||
|
||||
if(obj.contains("OptionalDependencies")) {
|
||||
js::array arr = obj["OptionalDependencies"].as_array();
|
||||
for(auto& iter : arr) {
|
||||
info.OptionalDependencies.push_back((std::string) iter.as_string());
|
||||
}
|
||||
}
|
||||
|
||||
} catch(const std::exception &exc) {
|
||||
LOG.warn() << "Не удалось прочитать " << mod_conf.string();
|
||||
}
|
||||
}
|
||||
} catch(const std::exception &exc) {
|
||||
LOG.warn() << "Не удалось прочитать моды из директории " << modsDir.string() << "\n" << exc.what();
|
||||
}
|
||||
|
||||
|
||||
return infos;
|
||||
}
|
||||
|
||||
void GameServer::stepConnections() {
|
||||
// Подключить новых игроков
|
||||
if(!External.NewConnectedPlayers.no_lock_readable().empty()) {
|
||||
@@ -1535,6 +1744,21 @@ void GameServer::stepSyncContent() {
|
||||
|
||||
if(!full.BinFont.empty())
|
||||
Content.Font.needResourceResponse(full.BinFont);
|
||||
|
||||
if(!full.Node.empty()) {
|
||||
std::unordered_map<DefNodeId_t, DefNode_t*> nodeDefines;
|
||||
|
||||
for(DefNodeId_t id : full.Node) {
|
||||
auto iter = Content.NodeDefines.find(id);
|
||||
if(iter != Content.NodeDefines.end()) {
|
||||
nodeDefines[id] = &iter->second;
|
||||
}
|
||||
}
|
||||
|
||||
for(std::shared_ptr<ContentEventController>& cec : Game.CECs) {
|
||||
cec->Remote->informateDefNode(nodeDefines);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -72,6 +72,10 @@ class GameServer : public AsyncObject {
|
||||
Font(ioc, zeroFont)
|
||||
{}
|
||||
|
||||
|
||||
std::map<DefNodeId_t, DefNode_t> NodeDefines;
|
||||
std::map<std::string, DefNodeId_t> NodeKeys;
|
||||
|
||||
} Content;
|
||||
|
||||
struct {
|
||||
@@ -157,6 +161,7 @@ class GameServer : public AsyncObject {
|
||||
RunCollect = Threads.size();
|
||||
RunCompress = Threads.size();
|
||||
Iteration += 1;
|
||||
assert(RunCollect != 0);
|
||||
Symaphore.notify_all();
|
||||
}
|
||||
|
||||
@@ -181,7 +186,7 @@ class GameServer : public AsyncObject {
|
||||
thread.join();
|
||||
}
|
||||
|
||||
void run(int id);
|
||||
__attribute__((optimize("O3"))) void run(int id);
|
||||
} BackingChunkPressure;
|
||||
|
||||
/*
|
||||
@@ -273,14 +278,23 @@ public:
|
||||
// Инициализация игрового протокола для сокета (onSocketAuthorized() может передать сокет в onSocketGame())
|
||||
coro<> pushSocketGameProtocol(tcp::socket socket, const std::string username);
|
||||
|
||||
/* Загрузит, сгенерирует или просто выдаст регион из мира, который должен существовать */
|
||||
Region* forceGetRegion(WorldId_t worldId, Pos::GlobalRegion pos);
|
||||
TexturePipeline buildTexturePipeline(const std::string& pipeline);
|
||||
std::string deBuildTexturePipeline(const TexturePipeline& pipeline);
|
||||
|
||||
private:
|
||||
void init(fs::path worldPath);
|
||||
void prerun();
|
||||
void run();
|
||||
|
||||
struct ModInfo {
|
||||
std::string Id, Title, Description;
|
||||
fs::path Path;
|
||||
|
||||
std::vector<std::string> Dependencies, OptionalDependencies;
|
||||
};
|
||||
|
||||
std::vector<ModInfo> readModDataPath(const fs::path& modsDir);
|
||||
|
||||
/*
|
||||
Подключение/отключение игроков
|
||||
*/
|
||||
|
||||
@@ -1 +1,12 @@
|
||||
#pragma once
|
||||
#pragma once
|
||||
|
||||
|
||||
namespace LV::Server {
|
||||
|
||||
class NodeDefManager {
|
||||
public:
|
||||
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <algorithm>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/system/system_error.hpp>
|
||||
#include <cstddef>
|
||||
#include <exception>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
@@ -136,8 +137,13 @@ bool RemoteClient::maybe_prepareChunkUpdate_Voxels(WorldId_t worldId, Pos::Globa
|
||||
for(const DefVoxelId_t& id : lostTypes) {
|
||||
auto iter = ResUses.RefDefVoxel.find(id);
|
||||
assert(iter != ResUses.RefDefVoxel.end()); // Должны быть описаны зависимости вокселя
|
||||
decrementBinary(std::move(iter->second.Texture), {}, std::move(iter->second.Sound), {}, {});
|
||||
decrementBinary(std::move(iter->second));
|
||||
ResUses.RefDefVoxel.erase(iter);
|
||||
|
||||
checkPacketBorder(16);
|
||||
NextPacket << (uint8_t) ToClient::L1::Definition
|
||||
<< (uint8_t) ToClient::L2Definition::FreeVoxel
|
||||
<< id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,8 +220,13 @@ bool RemoteClient::maybe_prepareChunkUpdate_Nodes(WorldId_t worldId, Pos::Global
|
||||
for(const DefNodeId_t& id : lostTypes) {
|
||||
auto iter = ResUses.RefDefNode.find(id);
|
||||
assert(iter != ResUses.RefDefNode.end()); // Должны быть описаны зависимости ноды
|
||||
decrementBinary({}, {}, std::move(iter->second.Sound), std::move(iter->second.Model), {});
|
||||
decrementBinary(std::move(iter->second));
|
||||
ResUses.RefDefNode.erase(iter);
|
||||
|
||||
checkPacketBorder(16);
|
||||
NextPacket << (uint8_t) ToClient::L1::Definition
|
||||
<< (uint8_t) ToClient::L2Definition::FreeNode
|
||||
<< id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,42 +249,49 @@ void RemoteClient::prepareRegionRemove(WorldId_t worldId, Pos::GlobalRegion regi
|
||||
// Уменьшаем зависимости вокселей и нод
|
||||
{
|
||||
auto iterWorld = ResUses.RefChunk.find(worldId);
|
||||
assert(iterWorld != ResUses.RefChunk.end());
|
||||
if(iterWorld == ResUses.RefChunk.end())
|
||||
return;
|
||||
|
||||
auto iterRegion = iterWorld->second.find(regionPos);
|
||||
if(iterRegion != iterWorld->second.end()) {
|
||||
for(const auto &iterChunk : iterRegion->second) {
|
||||
for(const DefVoxelId_t& id : iterChunk.Voxel) {
|
||||
auto iter = ResUses.DefVoxel.find(id);
|
||||
assert(iter != ResUses.DefVoxel.end()); // Воксель должен быть в зависимостях
|
||||
if(--iter->second == 0) {
|
||||
// Вокселя больше нет в зависимостях
|
||||
lostTypesV.push_back(id);
|
||||
ResUses.DefVoxel.erase(iter);
|
||||
}
|
||||
}
|
||||
|
||||
for(const DefNodeId_t& id : iterChunk.Node) {
|
||||
auto iter = ResUses.DefNode.find(id);
|
||||
assert(iter != ResUses.DefNode.end()); // Нода должна быть в зависимостях
|
||||
if(--iter->second == 0) {
|
||||
// Ноды больше нет в зависимостях
|
||||
lostTypesN.push_back(id);
|
||||
ResUses.DefNode.erase(iter);
|
||||
}
|
||||
if(iterRegion == iterWorld->second.end())
|
||||
return;
|
||||
|
||||
for(const auto &iterChunk : iterRegion->second) {
|
||||
for(const DefVoxelId_t& id : iterChunk.Voxel) {
|
||||
auto iter = ResUses.DefVoxel.find(id);
|
||||
assert(iter != ResUses.DefVoxel.end()); // Воксель должен быть в зависимостях
|
||||
if(--iter->second == 0) {
|
||||
// Вокселя больше нет в зависимостях
|
||||
lostTypesV.push_back(id);
|
||||
ResUses.DefVoxel.erase(iter);
|
||||
}
|
||||
}
|
||||
|
||||
iterWorld->second.erase(iterRegion);
|
||||
for(const DefNodeId_t& id : iterChunk.Node) {
|
||||
auto iter = ResUses.DefNode.find(id);
|
||||
assert(iter != ResUses.DefNode.end()); // Нода должна быть в зависимостях
|
||||
if(--iter->second == 0) {
|
||||
// Ноды больше нет в зависимостях
|
||||
lostTypesN.push_back(id);
|
||||
ResUses.DefNode.erase(iter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
iterWorld->second.erase(iterRegion);
|
||||
}
|
||||
|
||||
if(!lostTypesV.empty()) {
|
||||
for(const DefVoxelId_t& id : lostTypesV) {
|
||||
auto iter = ResUses.RefDefVoxel.find(id);
|
||||
assert(iter != ResUses.RefDefVoxel.end()); // Должны быть описаны зависимости вокселя
|
||||
decrementBinary(std::move(iter->second.Texture), {}, std::move(iter->second.Sound), {}, {});
|
||||
decrementBinary(std::move(iter->second));
|
||||
ResUses.RefDefVoxel.erase(iter);
|
||||
|
||||
checkPacketBorder(16);
|
||||
NextPacket << (uint8_t) ToClient::L1::Definition
|
||||
<< (uint8_t) ToClient::L2Definition::FreeVoxel
|
||||
<< id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,8 +299,13 @@ void RemoteClient::prepareRegionRemove(WorldId_t worldId, Pos::GlobalRegion regi
|
||||
for(const DefNodeId_t& id : lostTypesN) {
|
||||
auto iter = ResUses.RefDefNode.find(id);
|
||||
assert(iter != ResUses.RefDefNode.end()); // Должны быть описаны зависимости ноды
|
||||
decrementBinary({}, {}, std::move(iter->second.Sound), std::move(iter->second.Model), {});
|
||||
decrementBinary(std::move(iter->second));
|
||||
ResUses.RefDefNode.erase(iter);
|
||||
|
||||
checkPacketBorder(16);
|
||||
NextPacket << (uint8_t) ToClient::L1::Definition
|
||||
<< (uint8_t) ToClient::L2Definition::FreeNode
|
||||
<< id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,8 +345,7 @@ void RemoteClient::prepareEntityUpdate(ServerEntityId_t entityId, const Entity *
|
||||
if(--iterProfile->second == 0) {
|
||||
// Старый профиль больше не нужен
|
||||
auto iterProfileRef = ResUses.RefDefEntity.find(iterEntity->second.Profile);
|
||||
decrementBinary(std::move(iterProfileRef->second.Texture), std::move(iterProfileRef->second.Animation), {},
|
||||
std::move(iterProfileRef->second.Model), {});
|
||||
decrementBinary(std::move(iterProfileRef->second));
|
||||
ResUses.DefEntity.erase(iterProfile);
|
||||
}
|
||||
|
||||
@@ -360,7 +382,7 @@ void RemoteClient::prepareEntityRemove(ServerEntityId_t entityId)
|
||||
// Профиль больше не используется
|
||||
auto iterProfileRef = ResUses.RefDefEntity.find(iterEntity->second.Profile);
|
||||
|
||||
decrementBinary(std::move(iterProfileRef->second.Texture), std::move(iterProfileRef->second.Animation), {}, std::move(iterProfileRef->second.Model), {});
|
||||
decrementBinary(std::move(iterProfileRef->second));
|
||||
|
||||
ResUses.RefDefEntity.erase(iterProfileRef);
|
||||
ResUses.DefEntity.erase(iterProfile);
|
||||
@@ -408,7 +430,7 @@ void RemoteClient::prepareWorldUpdate(WorldId_t worldId, World* world)
|
||||
ResUses.DefWorld.erase(iterWorldProf);
|
||||
auto iterWorldProfRef = ResUses.RefDefWorld.find(iterWorld->second.Profile);
|
||||
assert(iterWorldProfRef != ResUses.RefDefWorld.end()); // Зависимости предыдущего профиля также должны быть
|
||||
decrementBinary(std::move(iterWorldProfRef->second.Texture), {}, {}, std::move(iterWorldProfRef->second.Model), {});
|
||||
decrementBinary(std::move(iterWorldProfRef->second));
|
||||
ResUses.RefDefWorld.erase(iterWorldProfRef);
|
||||
}
|
||||
}
|
||||
@@ -438,7 +460,7 @@ void RemoteClient::prepareWorldRemove(WorldId_t worldId)
|
||||
// Убавляем зависимости профиля
|
||||
auto iterWorldProfDef = ResUses.RefDefWorld.find(iterWorld->second.Profile);
|
||||
assert(iterWorldProfDef != ResUses.RefDefWorld.end()); // Зависимости профиля должны быть
|
||||
decrementBinary(std::move(iterWorldProfDef->second.Texture), {}, {}, std::move(iterWorldProfDef->second.Model), {});
|
||||
decrementBinary(std::move(iterWorldProfDef->second));
|
||||
ResUses.RefDefWorld.erase(iterWorldProfDef);
|
||||
}
|
||||
|
||||
@@ -468,83 +490,76 @@ ResourceRequest RemoteClient::pushPreparedPackets() {
|
||||
return std::move(NextRequest);
|
||||
}
|
||||
|
||||
void RemoteClient::informateBin(ToClient::L2Resource type, ResourceId_t id, const std::shared_ptr<ResourceFile>& data) {
|
||||
checkPacketBorder(0);
|
||||
NextPacket << (uint8_t) ToClient::L1::Resource // Оповещение
|
||||
<< (uint8_t) type << id;
|
||||
for(auto part : data->Hash)
|
||||
NextPacket << part;
|
||||
void RemoteClient::informateBinary(const std::vector<std::shared_ptr<ResourceFile>>& resources) {
|
||||
for(auto& resource : resources) {
|
||||
auto &hash = resource->Hash;
|
||||
|
||||
NextPacket << (uint8_t) ToClient::L1::Resource // Принудительная полная отправка
|
||||
<< (uint8_t) ToClient::L2Resource::InitResSend
|
||||
<< uint8_t(0) << uint8_t(0) << id
|
||||
<< uint32_t(data->Data.size());
|
||||
for(auto part : data->Hash)
|
||||
NextPacket << part;
|
||||
auto iter = std::find(NeedToSend.begin(), NeedToSend.end(), hash);
|
||||
if(iter == NeedToSend.end())
|
||||
continue; // Клиенту не требуется этот ресурс
|
||||
|
||||
NextPacket << uint8_t(0) << uint32_t(data->Data.size());
|
||||
{
|
||||
auto it = std::lower_bound(ClientBinaryCache.begin(), ClientBinaryCache.end(), hash);
|
||||
|
||||
size_t pos = 0;
|
||||
while(pos < data->Data.size()) {
|
||||
checkPacketBorder(0);
|
||||
size_t need = std::min(data->Data.size()-pos, std::min<size_t>(NextPacket.size(), 64000));
|
||||
NextPacket.write((const std::byte*) data->Data.data()+pos, need);
|
||||
pos += need;
|
||||
if(it == ClientBinaryCache.end() || *it != hash)
|
||||
ClientBinaryCache.insert(it, hash);
|
||||
}
|
||||
|
||||
// Полная отправка ресурса
|
||||
checkPacketBorder(2+4+32+4);
|
||||
NextPacket << (uint8_t) ToClient::L1::Resource // Принудительная полная отправка
|
||||
<< (uint8_t) ToClient::L2Resource::InitResSend
|
||||
<< uint32_t(resource->Data.size());
|
||||
for(auto part : hash)
|
||||
NextPacket << part;
|
||||
|
||||
NextPacket << uint32_t(resource->Data.size());
|
||||
|
||||
size_t pos = 0;
|
||||
while(pos < resource->Data.size()) {
|
||||
checkPacketBorder(0);
|
||||
size_t need = std::min(resource->Data.size()-pos, std::min<size_t>(NextPacket.size(), 64000));
|
||||
NextPacket.write((const std::byte*) resource->Data.data()+pos, need);
|
||||
pos += need;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteClient::informateBinTexture(const std::unordered_map<BinTextureId_t, std::shared_ptr<ResourceFile>> &textures)
|
||||
{
|
||||
for(auto pair : textures) {
|
||||
BinTextureId_t id = pair.first;
|
||||
if(!ResUses.BinTexture.contains(id))
|
||||
continue; // Клиент не наблюдает за этим объектом
|
||||
void RemoteClient::informateIdToHash(const std::vector<std::tuple<EnumBinResource, ResourceId_t, Hash_t>>& resourcesLink) {
|
||||
std::vector<std::tuple<EnumBinResource, ResourceId_t, Hash_t>> newForClient;
|
||||
|
||||
informateBin(ToClient::L2Resource::Texture, id, pair.second);
|
||||
for(auto& [type, id, hash] : resourcesLink) {
|
||||
// Посмотрим что известно клиенту
|
||||
auto iter = ResUses.BinUse[uint8_t(type)].find(id);
|
||||
if(iter != ResUses.BinUse[uint8_t(type)].end()) {
|
||||
if(std::get<1>(iter->second) != hash) {
|
||||
// Требуется перепривязать идентификатор к новому хешу
|
||||
newForClient.push_back({type, id, hash});
|
||||
std::get<1>(iter->second) = hash;
|
||||
// Проверить есть ли хеш на стороне клиента
|
||||
if(!std::binary_search(ClientBinaryCache.begin(), ClientBinaryCache.end(), hash)) {
|
||||
NeedToSend.push_back(hash);
|
||||
NextRequest.Hashes.push_back(hash);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Ресурс не отслеживается клиентом
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteClient::informateBinAnimation(const std::unordered_map<BinTextureId_t, std::shared_ptr<ResourceFile>> &textures)
|
||||
{
|
||||
for(auto pair : textures) {
|
||||
BinTextureId_t id = pair.first;
|
||||
if(!ResUses.BinTexture.contains(id))
|
||||
continue; // Клиент не наблюдает за этим объектом
|
||||
// Отправляем новые привязки ресурсов
|
||||
if(!newForClient.empty()) {
|
||||
assert(newForClient.size() < 65535*4);
|
||||
|
||||
informateBin(ToClient::L2Resource::Animation, id, pair.second);
|
||||
}
|
||||
}
|
||||
checkPacketBorder(2+4+newForClient.size()*(1+4+32));
|
||||
NextPacket << (uint8_t) ToClient::L1::Resource // Оповещение
|
||||
<< ((uint8_t) ToClient::L2Resource::Bind) << uint32_t(newForClient.size());
|
||||
|
||||
void RemoteClient::informateBinModel(const std::unordered_map<BinTextureId_t, std::shared_ptr<ResourceFile>> &textures)
|
||||
{
|
||||
for(auto pair : textures) {
|
||||
BinTextureId_t id = pair.first;
|
||||
if(!ResUses.BinTexture.contains(id))
|
||||
continue; // Клиент не наблюдает за этим объектом
|
||||
|
||||
informateBin(ToClient::L2Resource::Model, id, pair.second);
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteClient::informateBinSound(const std::unordered_map<BinTextureId_t, std::shared_ptr<ResourceFile>> &textures)
|
||||
{
|
||||
for(auto pair : textures) {
|
||||
BinTextureId_t id = pair.first;
|
||||
if(!ResUses.BinTexture.contains(id))
|
||||
continue; // Клиент не наблюдает за этим объектом
|
||||
|
||||
informateBin(ToClient::L2Resource::Sound, id, pair.second);
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteClient::informateBinFont(const std::unordered_map<BinTextureId_t, std::shared_ptr<ResourceFile>> &textures)
|
||||
{
|
||||
for(auto pair : textures) {
|
||||
BinTextureId_t id = pair.first;
|
||||
if(!ResUses.BinTexture.contains(id))
|
||||
continue; // Клиент не наблюдает за этим объектом
|
||||
|
||||
informateBin(ToClient::L2Resource::Font, id, pair.second);
|
||||
for(auto& [type, id, hash] : newForClient) {
|
||||
NextPacket << uint8_t(type) << uint32_t(id);
|
||||
NextPacket.write((const std::byte*) hash.data(), hash.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -561,16 +576,51 @@ void RemoteClient::informateDefVoxel(const std::unordered_map<DefVoxelId_t, void
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteClient::informateDefNode(const std::unordered_map<DefNodeId_t, void*> &nodes)
|
||||
void RemoteClient::informateDefNode(const std::unordered_map<DefNodeId_t, DefNode_t*> &nodes)
|
||||
{
|
||||
for(auto pair : nodes) {
|
||||
DefNodeId_t id = pair.first;
|
||||
for(auto& [id, def] : nodes) {
|
||||
if(!ResUses.DefNode.contains(id))
|
||||
continue;
|
||||
|
||||
size_t reserve = 0;
|
||||
for(int iter = 0; iter < 6; iter++)
|
||||
reserve += def->Texs[iter].Pipeline.size();
|
||||
|
||||
checkPacketBorder(1+1+4+1+2*6+reserve);
|
||||
NextPacket << (uint8_t) ToClient::L1::Definition
|
||||
<< (uint8_t) ToClient::L2Definition::Node
|
||||
<< id;
|
||||
<< id << (uint8_t) def->DrawType;
|
||||
|
||||
for(int iter = 0; iter < 6; iter++) {
|
||||
NextPacket << (uint16_t) def->Texs[iter].Pipeline.size();
|
||||
NextPacket.write((const std::byte*) def->Texs[iter].Pipeline.data(), def->Texs[iter].Pipeline.size());
|
||||
}
|
||||
|
||||
ResUsesObj::RefDefBin_t refs;
|
||||
{
|
||||
auto &array = refs.Resources[(uint8_t) EnumBinResource::Texture];
|
||||
for(int iter = 0; iter < 6; iter++) {
|
||||
array.insert(array.end(), def->Texs[iter].BinTextures.begin(), def->Texs[iter].BinTextures.end());
|
||||
}
|
||||
|
||||
std::sort(array.begin(), array.end());
|
||||
auto eraseLast = std::unique(array.begin(), array.end());
|
||||
array.erase(eraseLast, array.end());
|
||||
|
||||
incrementBinary(refs);
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
auto iterDefRef = ResUses.RefDefNode.find(id);
|
||||
if(iterDefRef != ResUses.RefDefNode.end()) {
|
||||
decrementBinary(std::move(iterDefRef->second));
|
||||
iterDefRef->second = std::move(refs);
|
||||
} else {
|
||||
ResUses.RefDefNode[id] = std::move(refs);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -692,103 +742,45 @@ coro<> RemoteClient::rP_System(Net::AsyncSocket &sock) {
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteClient::incrementBinary(const std::vector<BinTextureId_t>& textures, const std::vector<BinAnimationId_t>& animation,
|
||||
const std::vector<BinSoundId_t>& sounds, const std::vector<BinModelId_t>& models,
|
||||
const std::vector<BinFontId_t>& fonts
|
||||
) {
|
||||
for(BinTextureId_t id : textures) {
|
||||
if(++ResUses.BinTexture[id] == 1) {
|
||||
NextRequest.BinTexture.push_back(id);
|
||||
LOG.debug() << "Новое определение текстуры: " << id;
|
||||
}
|
||||
}
|
||||
void RemoteClient::incrementBinary(const ResUsesObj::RefDefBin_t& bin) {
|
||||
for(int iter = 0; iter < 5; iter++) {
|
||||
auto &use = ResUses.BinUse[iter];
|
||||
|
||||
for(BinAnimationId_t id : animation) {
|
||||
if(++ResUses.BinAnimation[id] == 1) {
|
||||
NextRequest.BinAnimation.push_back(id);
|
||||
LOG.debug() << "Новое определение анимации: " << id;
|
||||
}
|
||||
}
|
||||
|
||||
for(BinSoundId_t id : sounds) {
|
||||
if(++ResUses.BinSound[id] == 1) {
|
||||
NextRequest.BinSound.push_back(id);
|
||||
LOG.debug() << "Новое определение звука: " << id;
|
||||
}
|
||||
}
|
||||
|
||||
for(BinModelId_t id : models) {
|
||||
if(++ResUses.BinModel[id] == 1) {
|
||||
NextRequest.BinModel.push_back(id);
|
||||
LOG.debug() << "Новое определение модели: " << id;
|
||||
}
|
||||
}
|
||||
|
||||
for(BinFontId_t id : fonts) {
|
||||
if(++ResUses.BinFont[id] == 1) {
|
||||
NextRequest.BinFont.push_back(id);
|
||||
LOG.debug() << "Новое определение шрифта: " << id;
|
||||
for(ResourceId_t id : bin.Resources[iter]) {
|
||||
if(++std::get<0>(use[id]) == 1) {
|
||||
NextRequest.BinToHash[iter].push_back(id);
|
||||
LOG.debug() << "Новое определение (тип " << iter << ") -> " << id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteClient::decrementBinary(std::vector<BinTextureId_t>&& textures, std::vector<BinAnimationId_t>&& animation,
|
||||
std::vector<BinSoundId_t>&& sounds, std::vector<BinModelId_t>&& models,
|
||||
std::vector<BinFontId_t>&& fonts
|
||||
) {
|
||||
for(BinTextureId_t id : textures) {
|
||||
if(--ResUses.BinTexture[id] == 0) {
|
||||
ResUses.BinTexture.erase(ResUses.BinTexture.find(id));
|
||||
LOG.debug() << "Потеряно определение текстуры: " << id;
|
||||
void RemoteClient::decrementBinary(ResUsesObj::RefDefBin_t&& bin) {
|
||||
std::vector<std::tuple<EnumBinResource, ResourceId_t>> lost;
|
||||
|
||||
NextPacket << (uint8_t) ToClient::L1::Resource
|
||||
<< (uint8_t) ToClient::L2Resource::FreeTexture
|
||||
<< id;
|
||||
for(int iter = 0; iter < 5; iter++) {
|
||||
auto &use = ResUses.BinUse[iter];
|
||||
|
||||
for(ResourceId_t id : bin.Resources[iter]) {
|
||||
if(--std::get<0>(use[id]) == 0) {
|
||||
use.erase(use.find(id));
|
||||
|
||||
lost.push_back({(EnumBinResource) iter, id});
|
||||
LOG.debug() << "Потеряно определение (тип " << iter << ") -> " << id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(BinAnimationId_t id : animation) {
|
||||
if(--ResUses.BinAnimation[id] == 0) {
|
||||
ResUses.BinAnimation.erase(ResUses.BinAnimation.find(id));
|
||||
LOG.debug() << "Потеряно определение анимации: " << id;
|
||||
if(!lost.empty()) {
|
||||
assert(lost.size() < 65535*4);
|
||||
|
||||
NextPacket << (uint8_t) ToClient::L1::Resource
|
||||
<< (uint8_t) ToClient::L2Resource::FreeAnimation
|
||||
<< id;
|
||||
}
|
||||
}
|
||||
checkPacketBorder(1+1+4+lost.size()*(1+4));
|
||||
NextPacket << (uint8_t) ToClient::L1::Resource
|
||||
<< (uint8_t) ToClient::L2Resource::Lost
|
||||
<< uint32_t(lost.size());
|
||||
|
||||
for(BinSoundId_t id : sounds) {
|
||||
if(--ResUses.BinSound[id] == 0) {
|
||||
ResUses.BinSound.erase(ResUses.BinSound.find(id));
|
||||
LOG.debug() << "Потеряно определение звука: " << id;
|
||||
|
||||
NextPacket << (uint8_t) ToClient::L1::Resource
|
||||
<< (uint8_t) ToClient::L2Resource::FreeSound
|
||||
<< id;
|
||||
}
|
||||
}
|
||||
|
||||
for(BinModelId_t id : models) {
|
||||
if(--ResUses.BinModel[id] == 0) {
|
||||
ResUses.BinModel.erase(ResUses.BinModel.find(id));
|
||||
LOG.debug() << "Потеряно определение модели: " << id;
|
||||
|
||||
NextPacket << (uint8_t) ToClient::L1::Resource
|
||||
<< (uint8_t) ToClient::L2Resource::FreeModel
|
||||
<< id;
|
||||
}
|
||||
}
|
||||
|
||||
for(BinFontId_t id : fonts) {
|
||||
if(--ResUses.BinFont[id] == 0) {
|
||||
ResUses.BinFont.erase(ResUses.BinFont.find(id));
|
||||
LOG.debug() << "Потеряно определение шрифта: " << id;
|
||||
|
||||
NextPacket << (uint8_t) ToClient::L1::Resource
|
||||
<< (uint8_t) ToClient::L2Resource::FreeFont
|
||||
<< id;
|
||||
}
|
||||
for(auto& [type, id] : lost)
|
||||
NextPacket << uint8_t(type) << uint32_t(id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#include <unordered_set>
|
||||
|
||||
namespace LV::Server {
|
||||
using HASH = std::array<uint8_t, 32>;
|
||||
|
||||
template<typename ServerKey, typename ClientKey, std::enable_if_t<sizeof(ServerKey) >= sizeof(ClientKey), int> = 0>
|
||||
class CSChunkedMapper {
|
||||
@@ -140,11 +139,8 @@ public:
|
||||
этих ресурсов и переотправлять их клиенту
|
||||
*/
|
||||
struct ResourceRequest {
|
||||
std::vector<BinTextureId_t> BinTexture;
|
||||
std::vector<BinAnimationId_t> BinAnimation;
|
||||
std::vector<BinModelId_t> BinModel;
|
||||
std::vector<BinSoundId_t> BinSound;
|
||||
std::vector<BinFontId_t> BinFont;
|
||||
std::vector<Hash_t> Hashes;
|
||||
std::vector<ResourceId_t> BinToHash[5];
|
||||
|
||||
std::vector<DefVoxelId_t> Voxel;
|
||||
std::vector<DefNodeId_t> Node;
|
||||
@@ -154,11 +150,9 @@ struct ResourceRequest {
|
||||
std::vector<DefItemId_t> Item;
|
||||
|
||||
void insert(const ResourceRequest &obj) {
|
||||
BinTexture.insert(BinTexture.end(), obj.BinTexture.begin(), obj.BinTexture.end());
|
||||
BinAnimation.insert(BinAnimation.end(), obj.BinAnimation.begin(), obj.BinAnimation.end());
|
||||
BinModel.insert(BinModel.end(), obj.BinModel.begin(), obj.BinModel.end());
|
||||
BinSound.insert(BinSound.end(), obj.BinSound.begin(), obj.BinSound.end());
|
||||
BinFont.insert(BinFont.end(), obj.BinFont.begin(), obj.BinFont.end());
|
||||
Hashes.insert(Hashes.end(), obj.Hashes.begin(), obj.Hashes.end());
|
||||
for(int iter = 0; iter < 5; iter++)
|
||||
BinToHash[iter].insert(BinToHash[iter].end(), obj.BinToHash[iter].begin(), obj.BinToHash[iter].end());
|
||||
|
||||
Voxel.insert(Voxel.end(), obj.Voxel.begin(), obj.Voxel.end());
|
||||
Node.insert(Node.end(), obj.Node.begin(), obj.Node.end());
|
||||
@@ -169,9 +163,7 @@ struct ResourceRequest {
|
||||
}
|
||||
|
||||
void uniq() {
|
||||
for(std::vector<ResourceId_t> *vec : {
|
||||
&BinTexture, &BinAnimation, &BinModel, &BinSound,
|
||||
&BinFont, &Voxel, &Node, &World,
|
||||
for(std::vector<ResourceId_t> *vec : {&BinToHash, &Voxel, &Node, &World,
|
||||
&Portal, &Entity, &Item
|
||||
})
|
||||
{
|
||||
@@ -179,6 +171,10 @@ struct ResourceRequest {
|
||||
auto last = std::unique(vec->begin(), vec->end());
|
||||
vec->erase(last, vec->end());
|
||||
}
|
||||
|
||||
std::sort(Hashes.begin(), Hashes.end());
|
||||
auto last = std::unique(Hashes.begin(), Hashes.end());
|
||||
Hashes.erase(last, Hashes.end());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -199,7 +195,9 @@ class RemoteClient {
|
||||
DestroyLock UseLock;
|
||||
Net::AsyncSocket Socket;
|
||||
bool IsConnected = true, IsGoingShutdown = false;
|
||||
std::vector<HASH> ClientBinaryCache;
|
||||
|
||||
std::vector<Hash_t> ClientBinaryCache, // Хеши ресурсов которые есть у клиента
|
||||
NeedToSend; // Хеши которые нужно получить и отправить
|
||||
|
||||
/*
|
||||
При обнаружении нового контента составляется запрос (ResourceRequest)
|
||||
@@ -209,12 +207,8 @@ class RemoteClient {
|
||||
*/
|
||||
|
||||
struct ResUsesObj {
|
||||
// Счётчики использования двоичных кэшируемых ресурсов
|
||||
std::map<BinTextureId_t, uint32_t> BinTexture;
|
||||
std::map<BinAnimationId_t, uint32_t> BinAnimation;
|
||||
std::map<BinModelId_t, uint32_t> BinModel;
|
||||
std::map<BinSoundId_t, uint32_t> BinSound;
|
||||
std::map<BinFontId_t, uint32_t> BinFont;
|
||||
// Счётчики использования двоичных кэшируемых ресурсов + хэш привязанный к идентификатору
|
||||
std::map<ResourceId_t, std::tuple<uint32_t, Hash_t>> BinUse[5];
|
||||
|
||||
// Счётчики использование профилей контента
|
||||
std::map<DefVoxelId_t, uint32_t> DefVoxel; // Один чанк, одно использование
|
||||
@@ -226,39 +220,17 @@ class RemoteClient {
|
||||
|
||||
// Зависимость профилей контента от профилей ресурсов
|
||||
// Нужно чтобы пересчитать зависимости к профилям ресурсов
|
||||
struct RefDefVoxel_t {
|
||||
std::vector<BinTextureId_t> Texture;
|
||||
std::vector<BinSoundId_t> Sound;
|
||||
struct RefDefBin_t {
|
||||
std::vector<ResourceId_t> Resources[5];
|
||||
};
|
||||
std::map<DefVoxelId_t, RefDefVoxel_t> RefDefVoxel;
|
||||
struct RefDefNode_t {
|
||||
std::vector<BinModelId_t> Model;
|
||||
std::vector<BinSoundId_t> Sound;
|
||||
};
|
||||
std::map<DefNodeId_t, RefDefNode_t> RefDefNode;
|
||||
struct RefDefWorld_t {
|
||||
std::vector<BinTextureId_t> Texture;
|
||||
std::vector<BinModelId_t> Model;
|
||||
};
|
||||
std::map<WorldId_t, RefDefWorld_t> RefDefWorld;
|
||||
struct RefDefPortal_t {
|
||||
std::vector<BinTextureId_t> Texture;
|
||||
std::vector<BinAnimationId_t> Animation;
|
||||
std::vector<BinModelId_t> Model;
|
||||
};
|
||||
std::map<DefPortalId_t, RefDefPortal_t> RefDefPortal;
|
||||
struct RefDefEntity_t {
|
||||
std::vector<BinTextureId_t> Texture;
|
||||
std::vector<BinAnimationId_t> Animation;
|
||||
std::vector<BinModelId_t> Model;
|
||||
};
|
||||
std::map<DefEntityId_t, RefDefEntity_t> RefDefEntity;
|
||||
struct RefDefItem_t {
|
||||
std::vector<BinTextureId_t> Texture;
|
||||
std::vector<BinAnimationId_t> Animation;
|
||||
std::vector<BinModelId_t> Model;
|
||||
};
|
||||
std::map<DefItemId_t, RefDefItem_t> RefDefItem;
|
||||
|
||||
|
||||
std::map<DefVoxelId_t, RefDefBin_t> RefDefVoxel;
|
||||
std::map<DefNodeId_t, RefDefBin_t> RefDefNode;
|
||||
std::map<WorldId_t, RefDefBin_t> RefDefWorld;
|
||||
std::map<DefPortalId_t, RefDefBin_t> RefDefPortal;
|
||||
std::map<DefEntityId_t, RefDefBin_t> RefDefEntity;
|
||||
std::map<DefItemId_t, RefDefBin_t> RefDefItem;
|
||||
|
||||
// Модификационные зависимости экземпляров профилей контента
|
||||
struct ChunkRef {
|
||||
@@ -300,7 +272,7 @@ public:
|
||||
TOS::SpinlockObject<std::queue<uint8_t>> Actions;
|
||||
|
||||
public:
|
||||
RemoteClient(asio::io_context &ioc, tcp::socket socket, const std::string username, std::vector<HASH> &&client_cache)
|
||||
RemoteClient(asio::io_context &ioc, tcp::socket socket, const std::string username, std::vector<ResourceFile::Hash_t> &&client_cache)
|
||||
: LOG("RemoteClient " + username), Socket(ioc, std::move(socket)), Username(username), ClientBinaryCache(std::move(client_cache))
|
||||
{
|
||||
}
|
||||
@@ -366,16 +338,16 @@ public:
|
||||
// Сюда приходят все обновления ресурсов движка
|
||||
// Глобально их можно запросить в выдаче pushPreparedPackets()
|
||||
|
||||
// Двоичные файлы
|
||||
void informateBinTexture(const std::unordered_map<BinTextureId_t, std::shared_ptr<ResourceFile>> &textures);
|
||||
void informateBinAnimation(const std::unordered_map<BinAnimationId_t, std::shared_ptr<ResourceFile>> &animations);
|
||||
void informateBinModel(const std::unordered_map<BinModelId_t, std::shared_ptr<ResourceFile>> &models);
|
||||
void informateBinSound(const std::unordered_map<BinSoundId_t, std::shared_ptr<ResourceFile>> &sounds);
|
||||
void informateBinFont(const std::unordered_map<BinFontId_t, std::shared_ptr<ResourceFile>> &fonts);
|
||||
// Оповещение о ресурсе для отправки клиентам
|
||||
void informateBinary(const std::vector<std::shared_ptr<ResourceFile>>& resources);
|
||||
|
||||
// Привязывает локальный идентификатор с хешем. Если его нет у клиента,
|
||||
// то делается запрос на получение ресурсы для последующей отправки клиенту
|
||||
void informateIdToHash(const std::vector<std::tuple<EnumBinResource, ResourceId_t, Hash_t>>& resourcesLink);
|
||||
|
||||
// Игровые определения
|
||||
void informateDefVoxel(const std::unordered_map<DefVoxelId_t, void*> &voxels);
|
||||
void informateDefNode(const std::unordered_map<DefNodeId_t, void*> &nodes);
|
||||
void informateDefNode(const std::unordered_map<DefNodeId_t, DefNode_t*> &nodes);
|
||||
void informateDefWorld(const std::unordered_map<DefWorldId_t, void*> &worlds);
|
||||
void informateDefPortal(const std::unordered_map<DefPortalId_t, void*> &portals);
|
||||
void informateDefEntity(const std::unordered_map<DefEntityId_t, void*> &entityes);
|
||||
@@ -387,15 +359,8 @@ private:
|
||||
coro<> readPacket(Net::AsyncSocket &sock);
|
||||
coro<> rP_System(Net::AsyncSocket &sock);
|
||||
|
||||
void incrementBinary(const std::vector<BinTextureId_t> &textures, const std::vector<BinAnimationId_t> &animation,
|
||||
const std::vector<BinSoundId_t> &sounds, const std::vector<BinModelId_t> &models,
|
||||
const std::vector<BinFontId_t> &fonts
|
||||
);
|
||||
void decrementBinary(std::vector<BinTextureId_t>&& textures, std::vector<BinAnimationId_t>&& animation,
|
||||
std::vector<BinSoundId_t>&& sounds, std::vector<BinModelId_t>&& models,
|
||||
std::vector<BinFontId_t>&& fonts
|
||||
);
|
||||
void informateBin(ToClient::L2Resource type, ResourceId_t id, const std::shared_ptr<ResourceFile>& pair);
|
||||
void incrementBinary(const ResUsesObj::RefDefBin_t& bin);
|
||||
void decrementBinary(ResUsesObj::RefDefBin_t&& bin);
|
||||
|
||||
// void incrementProfile(const std::vector<TextureId_t> &textures, const std::vector<ModelId_t> &model,
|
||||
// const std::vector<SoundId_t> &sounds, const std::vector<FontId_t> &font
|
||||
|
||||
499
Src/sha2.hpp
Normal file
499
Src/sha2.hpp
Normal file
@@ -0,0 +1,499 @@
|
||||
// Copyright (c) 2018 Martyn Afford
|
||||
// Licensed under the MIT licence
|
||||
|
||||
#ifndef SHA2_HPP
|
||||
#define SHA2_HPP
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
namespace sha2 {
|
||||
|
||||
template <size_t N>
|
||||
using hash_array = std::array<uint8_t, N>;
|
||||
|
||||
using sha224_hash = hash_array<28>;
|
||||
using sha256_hash = hash_array<32>;
|
||||
using sha384_hash = hash_array<48>;
|
||||
using sha512_hash = hash_array<64>;
|
||||
|
||||
// SHA-2 uses big-endian integers.
|
||||
inline void
|
||||
write_u32(uint8_t* dest, uint32_t x)
|
||||
{
|
||||
*dest++ = (x >> 24) & 0xff;
|
||||
*dest++ = (x >> 16) & 0xff;
|
||||
*dest++ = (x >> 8) & 0xff;
|
||||
*dest++ = (x >> 0) & 0xff;
|
||||
}
|
||||
|
||||
inline void
|
||||
write_u64(uint8_t* dest, uint64_t x)
|
||||
{
|
||||
*dest++ = (x >> 56) & 0xff;
|
||||
*dest++ = (x >> 48) & 0xff;
|
||||
*dest++ = (x >> 40) & 0xff;
|
||||
*dest++ = (x >> 32) & 0xff;
|
||||
*dest++ = (x >> 24) & 0xff;
|
||||
*dest++ = (x >> 16) & 0xff;
|
||||
*dest++ = (x >> 8) & 0xff;
|
||||
*dest++ = (x >> 0) & 0xff;
|
||||
}
|
||||
|
||||
inline uint32_t
|
||||
read_u32(const uint8_t* src)
|
||||
{
|
||||
return static_cast<uint32_t>((src[0] << 24) | (src[1] << 16) |
|
||||
(src[2] << 8) | src[3]);
|
||||
}
|
||||
|
||||
inline uint64_t
|
||||
read_u64(const uint8_t* src)
|
||||
{
|
||||
uint64_t upper = read_u32(src);
|
||||
uint64_t lower = read_u32(src + 4);
|
||||
return ((upper & 0xffffffff) << 32) | (lower & 0xffffffff);
|
||||
}
|
||||
|
||||
// A compiler-recognised implementation of rotate right that avoids the
|
||||
// undefined behaviour caused by shifting by the number of bits of the left-hand
|
||||
// type. See John Regehr's article https://blog.regehr.org/archives/1063
|
||||
inline uint32_t
|
||||
ror(uint32_t x, uint32_t n)
|
||||
{
|
||||
return (x >> n) | (x << (-n & 31));
|
||||
}
|
||||
|
||||
inline uint64_t
|
||||
ror(uint64_t x, uint64_t n)
|
||||
{
|
||||
return (x >> n) | (x << (-n & 63));
|
||||
}
|
||||
|
||||
// Utility function to truncate larger hashes. Assumes appropriate hash types
|
||||
// (i.e., hash_array<N>) for type T.
|
||||
template <typename T, size_t N>
|
||||
inline T
|
||||
truncate(const hash_array<N>& hash)
|
||||
{
|
||||
T result;
|
||||
memcpy(result.data(), hash.data(), sizeof(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
// Both sha256_impl and sha512_impl are used by sha224/sha256 and
|
||||
// sha384/sha512 respectively, avoiding duplication as only the initial hash
|
||||
// values (s) and output hash length change.
|
||||
inline sha256_hash
|
||||
sha256_impl(const uint32_t* s, const uint8_t* data, uint64_t length)
|
||||
{
|
||||
static_assert(sizeof(uint32_t) == 4, "sizeof(uint32_t) must be 4");
|
||||
static_assert(sizeof(uint64_t) == 8, "sizeof(uint64_t) must be 8");
|
||||
|
||||
constexpr size_t chunk_bytes = 64;
|
||||
const uint64_t bit_length = length * 8;
|
||||
|
||||
uint32_t hash[8] = {s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7]};
|
||||
|
||||
constexpr uint32_t k[64] = {
|
||||
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
|
||||
0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
|
||||
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
|
||||
0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
||||
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
|
||||
0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
|
||||
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
|
||||
0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
||||
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
|
||||
0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
|
||||
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2};
|
||||
|
||||
auto chunk = [&hash, &k](const uint8_t* chunk_data) {
|
||||
uint32_t w[64] = {0};
|
||||
|
||||
for (int i = 0; i != 16; ++i) {
|
||||
w[i] = read_u32(&chunk_data[i * 4]);
|
||||
}
|
||||
|
||||
for (int i = 16; i != 64; ++i) {
|
||||
auto w15 = w[i - 15];
|
||||
auto w2 = w[i - 2];
|
||||
auto s0 = ror(w15, 7) ^ ror(w15, 18) ^ (w15 >> 3);
|
||||
auto s1 = ror(w2, 17) ^ ror(w2, 19) ^ (w2 >> 10);
|
||||
w[i] = w[i - 16] + s0 + w[i - 7] + s1;
|
||||
}
|
||||
|
||||
auto a = hash[0];
|
||||
auto b = hash[1];
|
||||
auto c = hash[2];
|
||||
auto d = hash[3];
|
||||
auto e = hash[4];
|
||||
auto f = hash[5];
|
||||
auto g = hash[6];
|
||||
auto h = hash[7];
|
||||
|
||||
for (int i = 0; i != 64; ++i) {
|
||||
auto s1 = ror(e, 6) ^ ror(e, 11) ^ ror(e, 25);
|
||||
auto ch = (e & f) ^ (~e & g);
|
||||
auto temp1 = h + s1 + ch + k[i] + w[i];
|
||||
auto s0 = ror(a, 2) ^ ror(a, 13) ^ ror(a, 22);
|
||||
auto maj = (a & b) ^ (a & c) ^ (b & c);
|
||||
auto temp2 = s0 + maj;
|
||||
|
||||
h = g;
|
||||
g = f;
|
||||
f = e;
|
||||
e = d + temp1;
|
||||
d = c;
|
||||
c = b;
|
||||
b = a;
|
||||
a = temp1 + temp2;
|
||||
}
|
||||
|
||||
hash[0] += a;
|
||||
hash[1] += b;
|
||||
hash[2] += c;
|
||||
hash[3] += d;
|
||||
hash[4] += e;
|
||||
hash[5] += f;
|
||||
hash[6] += g;
|
||||
hash[7] += h;
|
||||
};
|
||||
|
||||
while (length >= chunk_bytes) {
|
||||
chunk(data);
|
||||
data += chunk_bytes;
|
||||
length -= chunk_bytes;
|
||||
}
|
||||
|
||||
{
|
||||
std::array<uint8_t, chunk_bytes> buf;
|
||||
memcpy(buf.data(), data, length);
|
||||
|
||||
auto i = length;
|
||||
buf[i++] = 0x80;
|
||||
|
||||
if (i > chunk_bytes - 8) {
|
||||
while (i < chunk_bytes) {
|
||||
buf[i++] = 0;
|
||||
}
|
||||
|
||||
chunk(buf.data());
|
||||
i = 0;
|
||||
}
|
||||
|
||||
while (i < chunk_bytes - 8) {
|
||||
buf[i++] = 0;
|
||||
}
|
||||
|
||||
write_u64(&buf[i], bit_length);
|
||||
|
||||
chunk(buf.data());
|
||||
}
|
||||
|
||||
sha256_hash result;
|
||||
|
||||
for (uint8_t i = 0; i != 8; ++i) {
|
||||
write_u32(&result[i * 4], hash[i]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline sha512_hash
|
||||
sha512_impl(const uint64_t* s, const uint8_t* data, uint64_t length)
|
||||
{
|
||||
static_assert(sizeof(uint32_t) == 4, "sizeof(uint32_t) must be 4");
|
||||
static_assert(sizeof(uint64_t) == 8, "sizeof(uint64_t) must be 8");
|
||||
|
||||
constexpr size_t chunk_bytes = 128;
|
||||
const uint64_t bit_length_low = length << 3;
|
||||
const uint64_t bit_length_high = length >> (64 - 3);
|
||||
|
||||
uint64_t hash[8] = {s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7]};
|
||||
|
||||
constexpr uint64_t k[80] = {
|
||||
0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f,
|
||||
0xe9b5dba58189dbbc, 0x3956c25bf348b538, 0x59f111f1b605d019,
|
||||
0x923f82a4af194f9b, 0xab1c5ed5da6d8118, 0xd807aa98a3030242,
|
||||
0x12835b0145706fbe, 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2,
|
||||
0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235,
|
||||
0xc19bf174cf692694, 0xe49b69c19ef14ad2, 0xefbe4786384f25e3,
|
||||
0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65, 0x2de92c6f592b0275,
|
||||
0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5,
|
||||
0x983e5152ee66dfab, 0xa831c66d2db43210, 0xb00327c898fb213f,
|
||||
0xbf597fc7beef0ee4, 0xc6e00bf33da88fc2, 0xd5a79147930aa725,
|
||||
0x06ca6351e003826f, 0x142929670a0e6e70, 0x27b70a8546d22ffc,
|
||||
0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, 0x53380d139d95b3df,
|
||||
0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6,
|
||||
0x92722c851482353b, 0xa2bfe8a14cf10364, 0xa81a664bbc423001,
|
||||
0xc24b8b70d0f89791, 0xc76c51a30654be30, 0xd192e819d6ef5218,
|
||||
0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8,
|
||||
0x19a4c116b8d2d0c8, 0x1e376c085141ab53, 0x2748774cdf8eeb99,
|
||||
0x34b0bcb5e19b48a8, 0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb,
|
||||
0x5b9cca4f7763e373, 0x682e6ff3d6b2b8a3, 0x748f82ee5defb2fc,
|
||||
0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec,
|
||||
0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915,
|
||||
0xc67178f2e372532b, 0xca273eceea26619c, 0xd186b8c721c0c207,
|
||||
0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178, 0x06f067aa72176fba,
|
||||
0x0a637dc5a2c898a6, 0x113f9804bef90dae, 0x1b710b35131c471b,
|
||||
0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc,
|
||||
0x431d67c49c100d4c, 0x4cc5d4becb3e42b6, 0x597f299cfc657e2a,
|
||||
0x5fcb6fab3ad6faec, 0x6c44198c4a475817};
|
||||
|
||||
auto chunk = [&hash, &k](const uint8_t* chunk_data) {
|
||||
uint64_t w[80] = {0};
|
||||
|
||||
for (int i = 0; i != 16; ++i) {
|
||||
w[i] = read_u64(&chunk_data[i * 8]);
|
||||
}
|
||||
|
||||
for (int i = 16; i != 80; ++i) {
|
||||
auto w15 = w[i - 15];
|
||||
auto w2 = w[i - 2];
|
||||
auto s0 = ror(w15, 1) ^ ror(w15, 8) ^ (w15 >> 7);
|
||||
auto s1 = ror(w2, 19) ^ ror(w2, 61) ^ (w2 >> 6);
|
||||
w[i] = w[i - 16] + s0 + w[i - 7] + s1;
|
||||
}
|
||||
|
||||
auto a = hash[0];
|
||||
auto b = hash[1];
|
||||
auto c = hash[2];
|
||||
auto d = hash[3];
|
||||
auto e = hash[4];
|
||||
auto f = hash[5];
|
||||
auto g = hash[6];
|
||||
auto h = hash[7];
|
||||
|
||||
for (int i = 0; i != 80; ++i) {
|
||||
auto s1 = ror(e, 14) ^ ror(e, 18) ^ ror(e, 41);
|
||||
auto ch = (e & f) ^ (~e & g);
|
||||
auto temp1 = h + s1 + ch + k[i] + w[i];
|
||||
auto s0 = ror(a, 28) ^ ror(a, 34) ^ ror(a, 39);
|
||||
auto maj = (a & b) ^ (a & c) ^ (b & c);
|
||||
auto temp2 = s0 + maj;
|
||||
|
||||
h = g;
|
||||
g = f;
|
||||
f = e;
|
||||
e = d + temp1;
|
||||
d = c;
|
||||
c = b;
|
||||
b = a;
|
||||
a = temp1 + temp2;
|
||||
}
|
||||
|
||||
hash[0] += a;
|
||||
hash[1] += b;
|
||||
hash[2] += c;
|
||||
hash[3] += d;
|
||||
hash[4] += e;
|
||||
hash[5] += f;
|
||||
hash[6] += g;
|
||||
hash[7] += h;
|
||||
};
|
||||
|
||||
while (length >= chunk_bytes) {
|
||||
chunk(data);
|
||||
data += chunk_bytes;
|
||||
length -= chunk_bytes;
|
||||
}
|
||||
|
||||
{
|
||||
std::array<uint8_t, chunk_bytes> buf;
|
||||
memcpy(buf.data(), data, length);
|
||||
|
||||
auto i = length;
|
||||
buf[i++] = 0x80;
|
||||
|
||||
if (i > chunk_bytes - 16) {
|
||||
while (i < chunk_bytes) {
|
||||
buf[i++] = 0;
|
||||
}
|
||||
|
||||
chunk(buf.data());
|
||||
i = 0;
|
||||
}
|
||||
|
||||
while (i < chunk_bytes - 16) {
|
||||
buf[i++] = 0;
|
||||
}
|
||||
|
||||
write_u64(&buf[i + 0], bit_length_high);
|
||||
write_u64(&buf[i + 8], bit_length_low);
|
||||
|
||||
chunk(buf.data());
|
||||
}
|
||||
|
||||
sha512_hash result;
|
||||
|
||||
for (uint8_t i = 0; i != 8; ++i) {
|
||||
write_u64(&result[i * 8], hash[i]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline sha224_hash
|
||||
sha224(const uint8_t* data, uint64_t length)
|
||||
{
|
||||
// Second 32 bits of the fractional parts of the square roots of the ninth
|
||||
// through sixteenth primes 23..53
|
||||
const uint32_t initial_hash_values[8] = {0xc1059ed8,
|
||||
0x367cd507,
|
||||
0x3070dd17,
|
||||
0xf70e5939,
|
||||
0xffc00b31,
|
||||
0x68581511,
|
||||
0x64f98fa7,
|
||||
0xbefa4fa4};
|
||||
|
||||
auto hash = sha256_impl(initial_hash_values, data, length);
|
||||
return truncate<sha224_hash>(hash);
|
||||
}
|
||||
|
||||
inline sha256_hash
|
||||
sha256(const uint8_t* data, uint64_t length)
|
||||
{
|
||||
// First 32 bits of the fractional parts of the square roots of the first
|
||||
// eight primes 2..19:
|
||||
const uint32_t initial_hash_values[8] = {0x6a09e667,
|
||||
0xbb67ae85,
|
||||
0x3c6ef372,
|
||||
0xa54ff53a,
|
||||
0x510e527f,
|
||||
0x9b05688c,
|
||||
0x1f83d9ab,
|
||||
0x5be0cd19};
|
||||
|
||||
return sha256_impl(initial_hash_values, data, length);
|
||||
}
|
||||
|
||||
inline sha384_hash
|
||||
sha384(const uint8_t* data, uint64_t length)
|
||||
{
|
||||
const uint64_t initial_hash_values[8] = {0xcbbb9d5dc1059ed8,
|
||||
0x629a292a367cd507,
|
||||
0x9159015a3070dd17,
|
||||
0x152fecd8f70e5939,
|
||||
0x67332667ffc00b31,
|
||||
0x8eb44a8768581511,
|
||||
0xdb0c2e0d64f98fa7,
|
||||
0x47b5481dbefa4fa4};
|
||||
|
||||
auto hash = sha512_impl(initial_hash_values, data, length);
|
||||
return truncate<sha384_hash>(hash);
|
||||
}
|
||||
|
||||
inline sha512_hash
|
||||
sha512(const uint8_t* data, uint64_t length)
|
||||
{
|
||||
const uint64_t initial_hash_values[8] = {0x6a09e667f3bcc908,
|
||||
0xbb67ae8584caa73b,
|
||||
0x3c6ef372fe94f82b,
|
||||
0xa54ff53a5f1d36f1,
|
||||
0x510e527fade682d1,
|
||||
0x9b05688c2b3e6c1f,
|
||||
0x1f83d9abfb41bd6b,
|
||||
0x5be0cd19137e2179};
|
||||
|
||||
return sha512_impl(initial_hash_values, data, length);
|
||||
}
|
||||
|
||||
// SHA-512/t is a truncated version of SHA-512, where the result is truncated
|
||||
// to t bits (in this implementation, t must be a multiple of eight). The two
|
||||
// primariy variants of this are SHA-512/224 and SHA-512/256, both of which are
|
||||
// provided through explicit functions (sha512_224 and sha512_256) below this
|
||||
// function. On 64-bit platforms, SHA-512, and correspondingly SHA-512/t,
|
||||
// should give a significant performance improvement over SHA-224 and SHA-256
|
||||
// due to the doubled block size.
|
||||
template <int bits>
|
||||
inline hash_array<bits / 8>
|
||||
sha512_t(const uint8_t* data, uint64_t length)
|
||||
{
|
||||
static_assert(bits % 8 == 0, "Bits must be a multiple of 8 (i.e., bytes).");
|
||||
static_assert(0 < bits && bits <= 512, "Bits must be between 8 and 512");
|
||||
static_assert(bits != 384, "NIST explicitly denies 384 bits, use SHA-384.");
|
||||
|
||||
const uint64_t modified_initial_hash_values[8] = {
|
||||
0x6a09e667f3bcc908 ^ 0xa5a5a5a5a5a5a5a5,
|
||||
0xbb67ae8584caa73b ^ 0xa5a5a5a5a5a5a5a5,
|
||||
0x3c6ef372fe94f82b ^ 0xa5a5a5a5a5a5a5a5,
|
||||
0xa54ff53a5f1d36f1 ^ 0xa5a5a5a5a5a5a5a5,
|
||||
0x510e527fade682d1 ^ 0xa5a5a5a5a5a5a5a5,
|
||||
0x9b05688c2b3e6c1f ^ 0xa5a5a5a5a5a5a5a5,
|
||||
0x1f83d9abfb41bd6b ^ 0xa5a5a5a5a5a5a5a5,
|
||||
0x5be0cd19137e2179 ^ 0xa5a5a5a5a5a5a5a5};
|
||||
|
||||
// The SHA-512/t generation function uses a modified SHA-512 on the string
|
||||
// "SHA-512/t" where t is the number of bits. The modified SHA-512 operates
|
||||
// like the original but uses different initial hash values, as seen above.
|
||||
// The hash is then used for the initial hash values sent to the original
|
||||
// SHA-512. The sha512_224 and sha512_256 functions have this precalculated.
|
||||
constexpr int buf_size = 12;
|
||||
uint8_t buf[buf_size];
|
||||
|
||||
auto buf_ptr = reinterpret_cast<char*>(buf);
|
||||
auto len = snprintf(buf_ptr, buf_size, "SHA-512/%d", bits);
|
||||
auto ulen = static_cast<uint64_t>(len);
|
||||
|
||||
auto initial8 = sha512_impl(modified_initial_hash_values, buf, ulen);
|
||||
|
||||
// To read the hash bytes back into 64-bit integers, we must convert back
|
||||
// from big-endian.
|
||||
uint64_t initial64[8];
|
||||
|
||||
for (uint8_t i = 0; i != 8; ++i) {
|
||||
initial64[i] = read_u64(&initial8[i * 8]);
|
||||
}
|
||||
|
||||
// Once the initial hash is computed, use regular SHA-512 and copy the
|
||||
// appropriate number of bytes.
|
||||
auto hash = sha512_impl(initial64, data, length);
|
||||
return truncate<hash_array<bits / 8>>(hash);
|
||||
}
|
||||
|
||||
// It is preferable to use either sha512_224 or sha512_256 in place of
|
||||
// sha512_t<224> or sha512_t<256> for better performance (as the initial
|
||||
// hashes are precalculated), for slightly less syntactic noise and for
|
||||
// consistency with the other functions.
|
||||
inline sha224_hash
|
||||
sha512_224(const uint8_t* data, uint64_t length)
|
||||
{
|
||||
// Precalculated initial hash (The hash of "SHA-512/224" using the modified
|
||||
// SHA-512 generation function, described above in sha512_t).
|
||||
const uint64_t initial_hash_values[8] = {0x8c3d37c819544da2,
|
||||
0x73e1996689dcd4d6,
|
||||
0x1dfab7ae32ff9c82,
|
||||
0x679dd514582f9fcf,
|
||||
0x0f6d2b697bd44da8,
|
||||
0x77e36f7304c48942,
|
||||
0x3f9d85a86a1d36c8,
|
||||
0x1112e6ad91d692a1};
|
||||
|
||||
auto hash = sha512_impl(initial_hash_values, data, length);
|
||||
return truncate<sha224_hash>(hash);
|
||||
}
|
||||
|
||||
inline sha256_hash
|
||||
sha512_256(const uint8_t* data, uint64_t length)
|
||||
{
|
||||
// Precalculated initial hash (The hash of "SHA-512/256" using the modified
|
||||
// SHA-512 generation function, described above in sha512_t).
|
||||
const uint64_t initial_hash_values[8] = {0x22312194fc2bf72c,
|
||||
0x9f555fa3c84c64c2,
|
||||
0x2393b86b6f53b151,
|
||||
0x963877195940eabd,
|
||||
0x96283ee2a88effe3,
|
||||
0xbe5e1e2553863992,
|
||||
0x2b0199fc2c85b8aa,
|
||||
0x0eb72ddc81c52ca2};
|
||||
|
||||
auto hash = sha512_impl(initial_hash_values, data, length);
|
||||
return truncate<sha256_hash>(hash);
|
||||
}
|
||||
|
||||
} // sha2 namespace
|
||||
|
||||
#endif /* SHA2_HPP */
|
||||
@@ -71,4 +71,6 @@ vec4 atlasColor(uint texId, vec2 uv)
|
||||
|
||||
void main() {
|
||||
Frame = atlasColor(Fragment.Texture, Fragment.UV);
|
||||
if(Frame.w == 0)
|
||||
discard;
|
||||
}
|
||||
Binary file not shown.
BIN
assets/textures/frame.png
Normal file
BIN
assets/textures/frame.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 138 B |
Reference in New Issue
Block a user