@@ -1,9 +1,25 @@
# pragma once
# include <algorithm>
# include <array>
# include <cstdint>
# include <cstring>
# include <exception>
# include <filesystem>
# include <functional>
# include <fstream>
# include <memory>
# include <optional>
# include <stdexcept>
# include <string>
# include <string_view>
# include <tuple>
# include <unordered_map>
# include <unordered_set>
# include <utility>
# include <vector>
# include "Client/Vulkan/AtlasPipeline/TexturePipelineProgram.hpp"
# include "Common/Abstract.hpp"
# include "Common/Async.hpp"
# include "TOSAsync.hpp"
# include "boost/asio/executor.hpp"
@@ -17,106 +33,54 @@
Хранит все данные в оперативной памяти.
*/
enum class EnumAssets : int {
Nodestate , Particle , Animation , Model , Texture , Sound , Font , MAX_ENUM
} ;
using AssetsNodestate = uint32_t ;
using AssetsParticle = uint32_t ;
using AssetsAnimation = uint32_t ;
using AssetsModel = uint32_t ;
using AssetsTexture = uint32_t ;
using AssetsSound = uint32_t ;
using AssetsFont = uint32_t ;
static constexpr const char * EnumAssetsToDirectory ( EnumAssets value ) {
static constexpr const char * EnumAssetsToDirectory ( LV : : EnumAssets value ) {
switch ( value ) {
case EnumAssets : : Nodestate : return " nodestate " ;
case EnumAssets : : Particle : return " particles " ;
case EnumAssets : : Animation : return " animations " ;
case EnumAssets : : Model : return " models " ;
case EnumAssets : : Texture : return " textures " ;
case EnumAssets : : Sound : return " sounds " ;
case EnumAssets : : Font : return " fonts " ;
case LV : : EnumAssets: : Nodestate : return " nodestate " ;
case LV : : EnumAssets: : Particle : return " particle " ;
case LV : : EnumAssets: : Animation : return " animation " ;
case LV : : EnumAssets: : Model : return " model " ;
case LV : : EnumAssets: : Texture : return " texture " ;
case LV : : EnumAssets: : Sound : return " sound " ;
case LV : : EnumAssets: : Font : return " font " ;
default :
break ;
}
assert ( ! " Неизвестный тип медиаресурса " ) ;
return " " ;
}
namespace LV {
namespace fs = std : : filesystem ;
using AssetType = EnumAssets ;
struct ResourceFile {
using Hash_t = sha2 : : sha256_hash ; // boost::uuids::detail::sha1::digest_type;
Hash_t Hash ;
std : : vector < std : : byte > Data ;
std : : vector < uint8_t > Data ;
void calcHash ( ) {
Hash = sha2 : : sha256 ( ( const uint8_t * ) Data . data ( ) , Data . size ( ) ) ;
Hash = sha2 : : sha256 ( Data . data ( ) , Data . size ( ) ) ;
}
} ;
class AssetsPreloader : public TOS : : IAsyncDestructible {
public :
using Ptr = std : : shared_ptr < AssetsPreloader > ;
using IdTable = std : : unordered_map <
AssetType , // Тип р е с у р с а
std : : unordered_map <
std : : string , // Domain
std : : unordered_map <
std : : string , // Key
uint32_t // ResourceId
>
>
> ;
//
struct ReloadResult {
} ;
struct ReloadStatus {
/// TODO: callback'и для обновления статусов
/// TODO: многоуровневый статус std::vector<std::string>. Этапы/Шаги/Объекты
} ;
public :
static coro < Ptr > Create ( asio : : io_context & ioc ) ;
~ AssetsPreloader ( ) = default ;
AssetsPreloader ( const AssetsPreloader & ) = delete ;
AssetsPreloader ( AssetsPreloader & & ) = delete ;
AssetsPreloader & operator = ( const AssetsPreloader & ) = delete ;
AssetsPreloader & operator = ( AssetsPreloader & & ) = delete ;
// Пересматривает ресурсы и выдаёт изменения.
// Одновременно можно работать только один такой вызов.
// instances -> пути к директории с assets или архивы с assets внутри. От низшего приоритета к высшему.
// status -> обратный отклик о процессе обновления ресурсов.
// ReloadStatus <- новые и потерянные ресурсы.
coro < ReloadResult > reloadResources ( const std : : vector < fs : : path > & instances , ReloadStatus * status = nullptr ) {
bool expected = false ;
assert ( Reloading_ . compare_exchange_strong ( expected , true ) & & " Двойной вызов reloadResources " ) ;
try {
ReloadStatus secondStatus ;
co_return _reloadResources ( instances , status ? * status : secondStatus ) ;
} catch ( . . . ) {
assert ( ! " reloadResources: здесь не должно быть ошибок " ) ;
}
Reloading_ . exchange ( false ) ;
}
private :
struct ResourceFirstStageInfo {
// Путь к архиву (если есть), и путь до р е с у р с а
fs : : path ArchivePath , Path ;
// Время изменения файла
fs : : file_time_type Timestamp ;
} ;
struct ResourceSecondStageInfo : public ResourceFirstStageInfo {
// Обезличенный р е с у р с
std : : shared_ptr < std : : vector < uint8_t > > Resource ;
ResourceFile : : Hash_t Hash ;
// Сырой заголовок
std : : vector < std : : string > Dependencies ;
} ;
/*
Р е с у р с имеет бинарную часть, из который вырезаны все зависимости.
Вторая часть это заголовок, которые всегда динамично передаётся с сервера.
@@ -135,21 +99,364 @@ private:
std : : vector < uint8_t > Dependencies ;
} ;
AssetsPreloader ( asio : : io_context & ioc )
struct PendingDependency {
std : : string Domain , Key ;
} ;
struct PendingResource {
std : : string Domain , Key ;
fs : : file_time_type Timestamp ;
std : : shared_ptr < std : : vector < uint8_t > > Resource ;
ResourceFile : : Hash_t Hash ;
std : : vector < PendingDependency > ModelDeps ;
std : : vector < PendingDependency > TextureDeps ;
std : : vector < uint8_t > Extra ;
} ;
struct ResourceChangeObj {
std : : unordered_map < std : : string , std : : vector < std : : string > > Lost [ ( int ) AssetType : : MAX_ENUM ] ;
std : : unordered_map < std : : string , std : : vector < PendingResource > > NewOrChange [ ( int ) AssetType : : MAX_ENUM ] ;
} ;
struct Out_applyResourceChange {
std : : vector < uint32_t > Lost [ ( int ) AssetType : : MAX_ENUM ] ;
std : : vector < std : : pair < uint32_t , MediaResource > > NewOrChange [ ( int ) AssetType : : MAX_ENUM ] ;
} ;
struct ReloadStatus {
/// TODO: callback'и для обновления статусов
/// TODO: многоуровневый статус std::vector<std::string>. Этапы/Шаги/Объекты
} ;
struct AssetsRegister {
/*
Пути до активных папок assets, соответствую порядку загруженным модам.
От последнего мода к первому.
Тот файл, что был загружен раньше и будет использоваться
*/
std : : vector < fs : : path > Assets ;
/*
У этих ресурсов приоритет выше, если их удастся получить,
то использоваться будут именно они
Domain -> {key + data}
*/
std : : unordered_map < std : : string , std : : unordered_map < std : : string , void * > > Custom [ ( int ) AssetType : : MAX_ENUM ] ;
} ;
public :
static coro < Ptr > Create ( asio : : io_context & ioc ) ;
explicit AssetsPreloader ( asio : : io_context & ioc )
: TOS : : IAsyncDestructible ( ioc )
{
}
~ AssetsPreloader ( ) = default ;
AssetsPreloader ( const AssetsPreloader & ) = delete ;
AssetsPreloader ( AssetsPreloader & & ) = delete ;
AssetsPreloader & operator = ( const AssetsPreloader & ) = delete ;
AssetsPreloader & operator = ( AssetsPreloader & & ) = delete ;
// Пересматривает ресурсы и выдаёт изменения.
// Одновременно можно работать только один такой вызов.
// instances -> пути к директории с assets или архивы с assets внутри. От низшего приоритета к высшему.
// status -> обратный отклик о процессе обновления ресурсов.
// ReloadStatus <- новые и потерянные ресурсы.
coro < ResourceChangeObj > reloadResources ( const std : : vector < fs : : path > & instances , ReloadStatus * status = nullptr ) {
bool expected = false ;
assert ( Reloading_ . compare_exchange_strong ( expected , true ) & & " Двойной вызов reloadResources " ) ;
struct ReloadGuard {
std : : atomic < bool > & Flag ;
~ ReloadGuard ( ) { Flag . exchange ( false ) ; }
} guard { Reloading_ } ;
try {
ReloadStatus secondStatus ;
co_return co_await _reloadResources ( instances , status ? * status : secondStatus ) ;
} catch ( . . . ) {
assert ( ! " reloadResources: здесь не должно быть ошибок " ) ;
}
}
/*
Перепроверка изменений ресурсов по дате изменения, пересчёт хешей.
Обнаруженные изменения должны быть отправлены всем клиентам.
Ресурсы будут обработаны в подходящий формат и сохранены в кеше.
Одновременно может выполнятся только одна такая функция
Используется в GameServer
*/
coro < ResourceChangeObj > recheckResources ( const AssetsRegister & info ) {
return reloadResources ( info . Assets ) ;
}
// Синхронный вызов reloadResources
ResourceChangeObj reloadResourcesSync ( const std : : vector < fs : : path > & instances , ReloadStatus * status = nullptr ) {
asio : : io_context ioc ;
std : : optional < ResourceChangeObj > result ;
std : : exception_ptr error ;
asio : : co_spawn ( ioc , [ this , & instances , status , & result , & error ] ( ) - > coro < > {
try {
ReloadStatus localStatus ;
result = co_await reloadResources ( instances , status ? status : & localStatus ) ;
} catch ( . . . ) {
error = std : : current_exception ( ) ;
}
co_return ;
} , asio : : detached ) ;
ioc . run ( ) ;
if ( error )
std : : rethrow_exception ( error ) ;
if ( ! result )
return { } ;
return std : : move ( * result ) ;
}
// Синхронный вызов recheckResources
ResourceChangeObj recheckResourcesSync ( const AssetsRegister & info , ReloadStatus * status = nullptr ) {
return reloadResourcesSync ( info . Assets , status ) ;
}
/*
Применяет расчитанные изменения.
Раздаёт идентификаторы ресурсам и записывает их в таблицу
*/
Out_applyResourceChange applyResourceChange ( const ResourceChangeObj & orr ) ;
/*
Выдаёт идентификатор р е с у р с а , даже если он не существует или был удалён.
resource должен содержать домен и путь
*/
ResourceId getId ( AssetType type , const std : : string & domain , const std : : string & key ) ;
// Выдаёт р е с у р с по идентификатору
const MediaResource * getResource ( AssetType type , uint32_t id ) const ;
// Выдаёт р е с у р с по хешу
std : : optional < std : : tuple < AssetType , uint32_t , const MediaResource * > > getResource ( const ResourceFile : : Hash_t & hash ) ;
// Выдаёт зависимости к ресурсам профиля ноды
std : : tuple < AssetsNodestate , std : : vector < AssetsModel > , std : : vector < AssetsTexture > >
getNodeDependency ( const std : : string & domain , const std : : string & key ) ;
private :
struct ResourceFirstStageInfo {
// Путь к архиву (если есть), и путь до р е с у р с а
fs : : path ArchivePath , Path ;
// Время изменения файла
fs : : file_time_type Timestamp ;
} ;
struct ResourceSecondStageInfo : public ResourceFirstStageInfo {
// Обезличенный р е с у р с
std : : shared_ptr < std : : vector < uint8_t > > Resource ;
ResourceFile : : Hash_t Hash ;
// Сырой заголовок
std : : vector < uint8_t > Dependencies ;
} ;
// Текущее состояние reloadResources
std : : atomic < bool > Reloading_ = false ;
struct HeaderWriter {
std : : vector < uint8_t > Data ;
void writeU8 ( uint8_t value ) {
Data . push_back ( value ) ;
}
void writeU32 ( uint32_t value ) {
Data . push_back ( uint8_t ( value & 0xff ) ) ;
Data . push_back ( uint8_t ( ( value > > 8 ) & 0xff ) ) ;
Data . push_back ( uint8_t ( ( value > > 16 ) & 0xff ) ) ;
Data . push_back ( uint8_t ( ( value > > 24 ) & 0xff ) ) ;
}
void writeBytes ( const uint8_t * data , size_t size ) {
if ( size = = 0 )
return ;
const uint8_t * ptr = data ;
Data . insert ( Data . end ( ) , ptr , ptr + size ) ;
}
} ;
static ResourceFile readFileBytes ( const fs : : path & path ) {
std : : ifstream file ( path , std : : ios : : binary ) ;
if ( ! file )
throw std : : runtime_error ( " Н е удалось открыть файл: " + path . string ( ) ) ;
file . seekg ( 0 , std : : ios : : end ) ;
std : : streamoff size = file . tellg ( ) ;
if ( size < 0 )
size = 0 ;
file . seekg ( 0 , std : : ios : : beg ) ;
ResourceFile out ;
out . Data . resize ( static_cast < size_t > ( size ) ) ;
if ( size > 0 ) {
file . read ( reinterpret_cast < char * > ( out . Data . data ( ) ) , size ) ;
if ( ! file )
throw std : : runtime_error ( " Н е удалось прочитать файл: " + path . string ( ) ) ;
}
out . calcHash ( ) ;
return out ;
}
static std : : vector < uint8_t > toBytes ( std : : u8string_view data ) {
std : : vector < uint8_t > out ( data . size ( ) ) ;
if ( ! out . empty ( ) )
std : : memcpy ( out . data ( ) , data . data ( ) , out . size ( ) ) ;
return out ;
}
// Dependency lists are ordered by placeholder index used in the resource data.
static std : : vector < uint8_t > buildHeader ( AssetType type , const std : : vector < uint32_t > & modelDeps , const std : : vector < uint32_t > & textureDeps , const std : : vector < uint8_t > & extra ) {
HeaderWriter writer ;
writer . writeU8 ( ' a ' ) ;
writer . writeU8 ( ' h ' ) ;
writer . writeU8 ( 1 ) ;
writer . writeU8 ( static_cast < uint8_t > ( type ) ) ;
writer . writeU32 ( static_cast < uint32_t > ( modelDeps . size ( ) ) ) ;
for ( uint32_t id : modelDeps )
writer . writeU32 ( id ) ;
writer . writeU32 ( static_cast < uint32_t > ( textureDeps . size ( ) ) ) ;
for ( uint32_t id : textureDeps )
writer . writeU32 ( id ) ;
writer . writeU32 ( static_cast < uint32_t > ( extra . size ( ) ) ) ;
writer . writeBytes ( extra . data ( ) , extra . size ( ) ) ;
return std : : move ( writer . Data ) ;
}
static std : : vector < uint8_t > readOptionalMeta ( const fs : : path & path ) {
fs : : path metaPath = path ;
metaPath + = " .meta " ;
if ( ! fs : : exists ( metaPath ) | | ! fs : : is_regular_file ( metaPath ) )
return { } ;
ResourceFile meta = readFileBytes ( metaPath ) ;
return std : : move ( meta . Data ) ;
}
static std : : optional < uint32_t > findId ( const IdTable & table , AssetType type , const std : : string & domain , const std : : string & key ) {
auto iterType = table . find ( type ) ;
if ( iterType = = table . end ( ) )
return std : : nullopt ;
auto iterDomain = iterType - > second . find ( domain ) ;
if ( iterDomain = = iterType - > second . end ( ) )
return std : : nullopt ;
auto iterKey = iterDomain - > second . find ( key ) ;
if ( iterKey = = iterDomain - > second . end ( ) )
return std : : nullopt ;
return iterKey - > second ;
}
void ensureNextId ( AssetType type ) {
size_t index = static_cast < size_t > ( type ) ;
if ( NextIdInitialized [ index ] )
return ;
uint32_t maxId = 0 ;
auto iterType = DKToId . find ( type ) ;
if ( iterType ! = DKToId . end ( ) ) {
for ( const auto & [ domain , keys ] : iterType - > second ) {
for ( const auto & [ key , id ] : keys ) {
maxId = std : : max ( maxId , id + 1 ) ;
}
}
}
NextId [ index ] = maxId ;
NextIdInitialized [ index ] = true ;
}
struct HashHasher {
std : : size_t operator ( ) ( const ResourceFile : : Hash_t & hash ) const noexcept {
std : : size_t v = 14695981039346656037ULL ;
for ( const auto & byte : hash ) {
v ^ = static_cast < std : : size_t > ( byte ) ;
v * = 1099511628211ULL ;
}
return v ;
}
} ;
struct ParsedHeader {
AssetType Type = AssetType : : MAX_ENUM ;
std : : vector < uint32_t > ModelDeps ;
std : : vector < uint32_t > TextureDeps ;
std : : vector < uint8_t > Extra ;
} ;
static std : : optional < ParsedHeader > parseHeader ( const std : : vector < uint8_t > & data ) {
size_t pos = 0 ;
auto readU8 = [ & ] ( uint8_t & out ) - > bool {
if ( pos + 1 > data . size ( ) )
return false ;
out = data [ pos + + ] ;
return true ;
} ;
auto readU32 = [ & ] ( uint32_t & out ) - > bool {
if ( pos + 4 > data . size ( ) )
return false ;
out = uint32_t ( data [ pos ] ) |
( uint32_t ( data [ pos + 1 ] ) < < 8 ) |
( uint32_t ( data [ pos + 2 ] ) < < 16 ) |
( uint32_t ( data [ pos + 3 ] ) < < 24 ) ;
pos + = 4 ;
return true ;
} ;
ParsedHeader out ;
uint8_t c0 , c1 , version , type ;
if ( ! readU8 ( c0 ) | | ! readU8 ( c1 ) | | ! readU8 ( version ) | | ! readU8 ( type ) )
return std : : nullopt ;
if ( c0 ! = ' a ' | | c1 ! = ' h ' | | version ! = 1 )
return std : : nullopt ;
out . Type = static_cast < AssetType > ( type ) ;
uint32_t count = 0 ;
if ( ! readU32 ( count ) )
return std : : nullopt ;
out . ModelDeps . reserve ( count ) ;
for ( uint32_t i = 0 ; i < count ; i + + ) {
uint32_t id ;
if ( ! readU32 ( id ) )
return std : : nullopt ;
out . ModelDeps . push_back ( id ) ;
}
if ( ! readU32 ( count ) )
return std : : nullopt ;
out . TextureDeps . reserve ( count ) ;
for ( uint32_t i = 0 ; i < count ; i + + ) {
uint32_t id ;
if ( ! readU32 ( id ) )
return std : : nullopt ;
out . TextureDeps . push_back ( id ) ;
}
uint32_t extraSize = 0 ;
if ( ! readU32 ( extraSize ) )
return std : : nullopt ;
if ( pos + extraSize > data . size ( ) )
return std : : nullopt ;
out . Extra . assign ( data . begin ( ) + pos , data . begin ( ) + pos + extraSize ) ;
return out ;
}
// Пересмотр ресурсов
coro < ReloadResult > _reloadResources ( const std : : vector < fs : : path > & instances , ReloadStatus & status ) const {
coro < ResourceChangeObj > _reloadResources ( const std : : vector < fs : : path > & instances , ReloadStatus & status ) const {
ResourceChangeObj result ;
// 1) Поиск всех ресурсов и построение конечной карты ресурсов (timestamps, path, name, size)
// Карта найденных ресурсов
std : : unordered_map <
Enum Assets , // Тип р е с у р с а
AssetType , // Тип р е с у р с а
std : : unordered_map <
std : : string , // Domain
std : : unordered_map <
@@ -166,42 +473,62 @@ private:
/// TODO: пока не поддерживается
} else if ( fs : : is_directory ( instance ) ) {
// Директория
fs : : path assets = instance / " assets " ;
if ( fs : : exists ( assets ) & & fs : : is_directory ( assets ) ) {
// Директорию assets существует, перебираем домены в ней
for ( auto begin = fs : : directory_iterator ( assets ) , end = fs : : directory_iterator ( ) ; begin ! = end ; begin + + ) {
if ( ! begin - > is_directory ( ) )
fs : : path assetsRoot = instance ;
fs : : path assetsCandidate = instance / " assets " ;
if ( fs : : exists ( assetsCandidate ) & & fs : : is_directory ( assetsCandidate ) )
assetsRoot = assetsCandidate ;
// Директория assets существует, перебираем домены в ней
for ( auto begin = fs : : directory_iterator ( assetsRoot ) , end = fs : : directory_iterator ( ) ; begin ! = end ; begin + + ) {
if ( ! begin - > is_directory ( ) )
continue ;
/// TODO: выглядит всё не очень асинхронно
co_await asio : : post ( co_await asio : : this_coro : : executor ) ;
fs : : path domainPath = begin - > path ( ) ;
std : : string domain = domainPath . filename ( ) . string ( ) ;
// Перебираем по типу р е с у р с а
for ( int type = 0 ; type < static_cast < int > ( AssetType : : MAX_ENUM ) ; type + + ) {
AssetType assetType = static_cast < AssetType > ( type ) ;
fs : : path assetPath = domainPath / EnumAssetsToDirectory ( assetType ) ;
if ( ! fs : : exists ( assetPath ) | | ! fs : : is_directory ( assetPath ) )
continue ;
/// TODO: выглядит всё не очень асинхронно
co_await asio : : post ( co_await asio : : this_coro : : executor ) ;
std : : unordered_map <
std : : string , // Key
ResourceFirstStageInfo // ResourceInfo
> & firstStage = resourcesFirstStage [ assetType ] [ domain ] ;
fs : : path domainPath = begin - > path ( ) ;
std : : string domain = domainPath . filename ( ) ;
// Исследуем все ресурсы одного типа
for ( auto begin = fs : : recursive_directory_iterator ( assetPath ) , end = fs : : recursive_directory_iterator ( ) ; begin ! = end ; begin + + ) {
if ( begin - > is_directory ( ) )
continue ;
// Перебираем по типу р е с у р с а
for ( EnumAssets assetType = EnumAssets ( 0 ) ; a ssetType < EnumAssets : : MAX_ENUM ; ( ( int & ) assetType ) + + ) {
fs : : path assetPath = domainPath / EnumAssetsToDirectory ( assetType ) ;
fs : : path file = begin - > path ( ) ;
i f ( assetType = = A ssetType: : Texture & & file . extension ( ) = = " .meta " )
continue ;
std : : string key = fs : : relative ( file , assetPath ) . string ( ) ;
if ( firstStage . contains ( key ) )
continue ;
std : : unordered_map <
std : : string , // Key
ResourceFirstStageInfo // ResourceInfo
> & firstStage = resourcesFirstStage [ assetType ] [ domain ] ;
// Исследуем все ресурсы одного типа
for ( auto begin = fs : : recursive_directory_iterator ( assetPath ) , end = fs : : recursive_directory_iterator ( ) ; begin ! = end ; begin + + ) {
if ( begin - > is_directory ( ) )
continue ;
fs : : path file = begin - > path ( ) ;
std : : string key = fs : : relative ( file , domainPath ) . string ( ) ;
// Работаем с ресурсом
firstStage [ key ] = ResourceFirstStageInfo {
. Path = file ,
. Timestamp = fs : : last_write_time ( file )
} ;
f s: : file_time_type timestamp = fs : : last_write_time ( file ) ;
if ( assetType = = AssetType : : Texture ) {
fs : : path metaPath = file ;
metaPath + = " .meta " ;
if ( fs : : exists ( metaPath ) & & fs : : is_regular_file ( metaPath ) ) {
auto metaTime = fs : : last_write_time ( metaPath ) ;
if ( metaTime > timestamp )
timestamp = metaTime ;
}
}
// Работаем с ресурсом
firstStage [ key ] = ResourceFirstStageInfo {
. Path = file ,
. Timestamp = timestamp
} ;
}
}
}
@@ -214,40 +541,354 @@ private:
}
}
auto buildResource = [ & ] ( AssetType type , const std : : string & domain , const std : : string & key , const ResourceFirstStageInfo & info ) - > PendingResource {
PendingResource out ;
out . Domain = domain ;
out . Key = key ;
out . Timestamp = info . Timestamp ;
if ( type = = AssetType : : Nodestate ) {
ResourceFile file = readFileBytes ( info . Path ) ;
std : : string_view view ( reinterpret_cast < const char * > ( file . Data . data ( ) ) , file . Data . size ( ) ) ;
js : : object obj = js : : parse ( view ) . as_object ( ) ;
PreparedNodeState pns ( domain , obj ) ;
pns . LocalToModel . reserve ( pns . LocalToModelKD . size ( ) ) ;
uint32_t placeholder = 0 ;
for ( const auto & [ subDomain , subKey ] : pns . LocalToModelKD ) {
pns . LocalToModel . push_back ( placeholder + + ) ;
out . ModelDeps . push_back ( { subDomain , subKey } ) ;
}
std : : vector < uint8_t > data = toBytes ( pns . dump ( ) ) ;
out . Resource = std : : make_shared < std : : vector < uint8_t > > ( std : : move ( data ) ) ;
out . Hash = sha2 : : sha256 ( out . Resource - > data ( ) , out . Resource - > size ( ) ) ;
} else if ( type = = AssetType : : Model ) {
const std : : string ext = info . Path . extension ( ) . string ( ) ;
if ( ext = = " .json " ) {
ResourceFile file = readFileBytes ( info . Path ) ;
std : : string_view view ( reinterpret_cast < const char * > ( file . Data . data ( ) ) , file . Data . size ( ) ) ;
js : : object obj = js : : parse ( view ) . as_object ( ) ;
PreparedModel pm ( domain , obj ) ;
pm . CompiledTextures . clear ( ) ;
pm . CompiledTextures . reserve ( pm . Textures . size ( ) ) ;
std : : unordered_map < std : : string , uint32_t > textureIndex ;
auto getTexturePlaceholder = [ & ] ( const std : : string & texDomain , const std : : string & texKey ) - > uint32_t {
std : : string token ;
token . reserve ( texDomain . size ( ) + texKey . size ( ) + 1 ) ;
token . append ( texDomain ) ;
token . push_back ( ' : ' ) ;
token . append ( texKey ) ;
auto iter = textureIndex . find ( token ) ;
if ( iter ! = textureIndex . end ( ) )
return iter - > second ;
uint32_t placeholderId = static_cast < uint32_t > ( out . TextureDeps . size ( ) ) ;
textureIndex . emplace ( std : : move ( token ) , placeholderId ) ;
out . TextureDeps . push_back ( { texDomain , texKey } ) ;
return placeholderId ;
} ;
for ( const auto & [ tkey , pipeline ] : pm . Textures ) {
TexturePipeline compiled ;
if ( pipeline . IsSource ) {
TexturePipelineProgram program ;
std : : string source ( reinterpret_cast < const char * > ( pipeline . Pipeline . data ( ) ) , pipeline . Pipeline . size ( ) ) ;
std : : string err ;
if ( ! program . compile ( source , & err ) ) {
throw std : : runtime_error ( " Ошибка компиляции pipeline: " + err ) ;
}
auto resolver = [ & ] ( std : : string_view name ) - > std : : optional < uint32_t > {
auto [ texDomain , texKey ] = parseDomainKey ( std : : string ( name ) , domain ) ;
return getTexturePlaceholder ( texDomain , texKey ) ;
} ;
if ( ! program . link ( resolver , & err ) ) {
throw std : : runtime_error ( " Ошибка линковки pipeline: " + err ) ;
}
const std : : vector < uint8_t > bytes = program . toBytes ( ) ;
compiled . Pipeline . resize ( bytes . size ( ) ) ;
if ( ! bytes . empty ( ) ) {
std : : memcpy ( compiled . Pipeline . data ( ) , bytes . data ( ) , bytes . size ( ) ) ;
}
} else {
compiled . Pipeline = pipeline . Pipeline ;
}
for ( const auto & [ texDomain , texKey ] : pipeline . Assets ) {
uint32_t placeholderId = getTexturePlaceholder ( texDomain , texKey ) ;
compiled . BinTextures . push_back ( placeholderId ) ;
}
pm . CompiledTextures [ tkey ] = std : : move ( compiled ) ;
}
for ( const auto & sub : pm . SubModels ) {
out . ModelDeps . push_back ( { sub . Domain , sub . Key } ) ;
}
std : : vector < uint8_t > data = toBytes ( pm . dump ( ) ) ;
out . Resource = std : : make_shared < std : : vector < uint8_t > > ( std : : move ( data ) ) ;
out . Hash = sha2 : : sha256 ( out . Resource - > data ( ) , out . Resource - > size ( ) ) ;
} else if ( ext = = " .gltf " | | ext = = " .glb " ) {
ResourceFile file = readFileBytes ( info . Path ) ;
out . Resource = std : : make_shared < std : : vector < uint8_t > > ( std : : move ( file . Data ) ) ;
out . Hash = file . Hash ;
} else {
throw std : : runtime_error ( " Н е поддерживаемый формат модели: " + info . Path . string ( ) ) ;
}
} else if ( type = = AssetType : : Texture ) {
ResourceFile file = readFileBytes ( info . Path ) ;
out . Resource = std : : make_shared < std : : vector < uint8_t > > ( std : : move ( file . Data ) ) ;
out . Hash = file . Hash ;
out . Extra = readOptionalMeta ( info . Path ) ;
} else {
ResourceFile file = readFileBytes ( info . Path ) ;
out . Resource = std : : make_shared < std : : vector < uint8_t > > ( std : : move ( file . Data ) ) ;
out . Hash = file . Hash ;
}
return out ;
} ;
// 2) Обрабатываться будут только изменённые (новый timestamp) или новые ресурсы
// .meta
// Текстуры, шрифты, звуки хранить как есть
// У моделей, состояний нод, анимации, частиц обналичить зависимости
// Мета влияет только на хедер
/// TODO: реализовать реформатирование новых и изменённых ресурсов во внутренний обезличенный формат
for ( const auto & [ type , resources ] : MediaResources ) {
auto iterType = resourcesFirstStage . find ( type ) ;
for ( const auto & [ id , resource ] : resources ) {
if ( iterType = = resourcesFirstStage . end ( ) ) {
result . Lost [ ( int ) type ] [ resource . Domain ] . push_back ( resource . Key ) ;
continue ;
}
co_await asio : : post ( co_await asio : : this_coro : : executor ) ;
auto iterDomain = iterType - > second . find ( resource . Domain ) ;
if ( iterDomain = = iterType - > second . end ( ) ) {
result . Lost [ ( int ) type ] [ resource . Domain ] . push_back ( resource . Key ) ;
continue ;
}
asio : : experimental : : channel < void ( ) > ch ( IOC , 8 ) ;
if ( ! iterDomain - > second . contains ( resource . Key ) ) {
result . Lost [ ( int ) type ] [ resource . Domain ] . push_back ( resource . Key ) ;
}
}
}
co_return ReloadResult { } ;
for ( const auto & [ type , domains ] : resourcesFirstStage ) {
for ( const auto & [ domain , table ] : domains ) {
for ( const auto & [ key , info ] : table ) {
bool needsUpdate = true ;
if ( auto existingId = findId ( DKToId , type , domain , key ) ) {
auto iterType = MediaResources . find ( type ) ;
if ( iterType ! = MediaResources . end ( ) ) {
auto iterRes = iterType - > second . find ( * existingId ) ;
if ( iterRes ! = iterType - > second . end ( ) & & iterRes - > second . Timestamp = = info . Timestamp ) {
needsUpdate = false ;
}
}
}
if ( ! needsUpdate )
continue ;
co_await asio : : post ( co_await asio : : this_coro : : executor ) ;
PendingResource resource = buildResource ( type , domain , key , info ) ;
result . NewOrChange [ ( int ) type ] [ domain ] . push_back ( std : : move ( resource ) ) ;
}
}
}
co_return result ;
}
std : : unordered_map <
EnumAssets , // Тип р е с у р с а
std : : unordered_map <
std : : string , // Domain
std : : unordered_map <
std : : string , // Key
uint32_t // ResourceId
>
>
> DKToId ;
std : : unordered_map <
EnumAssets , // Тип р е с у р с а
std : : unordered_map <
uint32_t ,
MediaResource // ResourceInfo
>
> MediaResources ;
IdTable DKToId ;
std : : unordered_map < AssetType , std : : unordered_map < uint32_t , MediaResource > > MediaResources ;
std : : unordered_map < ResourceFile : : Hash_t , std : : pair < AssetType , uint32_t > , HashHasher > HashToId ;
std : : array < uint32_t , static_cast < size_t > ( AssetType : : MAX_ENUM ) > NextId = { } ;
std : : array < bool , static_cast < size_t > ( AssetType : : MAX_ENUM ) > NextIdInitialized = { } ;
} ;
inline AssetsPreloader : : Out_applyResourceChange AssetsPreloader : : applyResourceChange ( const ResourceChangeObj & orr ) {
Out_applyResourceChange result ;
// Удаляем ресурсы
/*
Удаляются только ресурсы, при этом за ними остаётся бронь на идентификатор
Уже скомпилированные зависимости к ресурсам не будут
перекомпилироваться для смены идентификатора. Если нужный р е с у р с
появится, то привязка останется. Новые клиенты не получат р е с у р с
которого нет, но он может использоваться
*/
for ( int type = 0 ; type < ( int ) AssetType : : MAX_ENUM ; type + + ) {
for ( const auto & [ domain , keys ] : orr . Lost [ type ] ) {
auto iterType = DKToId . find ( static_cast < AssetType > ( type ) ) ;
if ( iterType = = DKToId . end ( ) )
continue ;
auto iterDomain = iterType - > second . find ( domain ) ;
if ( iterDomain = = iterType - > second . end ( ) )
continue ;
for ( const auto & key : keys ) {
auto iterKey = iterDomain - > second . find ( key ) ;
if ( iterKey = = iterDomain - > second . end ( ) )
continue ;
uint32_t id = iterKey - > second ;
result . Lost [ type ] . push_back ( id ) ;
auto iterResType = MediaResources . find ( static_cast < AssetType > ( type ) ) ;
if ( iterResType = = MediaResources . end ( ) )
continue ;
auto iterRes = iterResType - > second . find ( id ) ;
if ( iterRes = = iterResType - > second . end ( ) )
continue ;
HashToId . erase ( iterRes - > second . Hash ) ;
iterResType - > second . erase ( iterRes ) ;
}
}
}
// Добавляем
for ( int type = 0 ; type < ( int ) AssetType : : MAX_ENUM ; type + + ) {
for ( const auto & [ domain , resources ] : orr . NewOrChange [ type ] ) {
for ( const PendingResource & pending : resources ) {
uint32_t id = getId ( static_cast < AssetType > ( type ) , pending . Domain , pending . Key ) ;
std : : vector < uint32_t > modelIds ;
modelIds . reserve ( pending . ModelDeps . size ( ) ) ;
for ( const auto & dep : pending . ModelDeps )
modelIds . push_back ( getId ( AssetType : : Model , dep . Domain , dep . Key ) ) ;
std : : vector < uint32_t > textureIds ;
textureIds . reserve ( pending . TextureDeps . size ( ) ) ;
for ( const auto & dep : pending . TextureDeps )
textureIds . push_back ( getId ( AssetType : : Texture , dep . Domain , dep . Key ) ) ;
MediaResource resource ;
resource . Domain = pending . Domain ;
resource . Key = pending . Key ;
resource . Timestamp = pending . Timestamp ;
resource . Resource = pending . Resource ;
resource . Hash = pending . Hash ;
resource . Dependencies = buildHeader ( static_cast < AssetType > ( type ) , modelIds , textureIds , pending . Extra ) ;
auto & table = MediaResources [ static_cast < AssetType > ( type ) ] ;
if ( auto iter = table . find ( id ) ; iter ! = table . end ( ) )
HashToId . erase ( iter - > second . Hash ) ;
table [ id ] = resource ;
HashToId [ resource . Hash ] = { static_cast < AssetType > ( type ) , id } ;
result . NewOrChange [ type ] . push_back ( { id , std : : move ( resource ) } ) ;
}
}
std : : unordered_set < uint32_t > changed ;
for ( const auto & [ id , _ ] : result . NewOrChange [ type ] )
changed . insert ( id ) ;
auto & lost = result . Lost [ type ] ;
lost . erase ( std : : remove_if ( lost . begin ( ) , lost . end ( ) ,
[ & ] ( uint32_t id ) { return changed . contains ( id ) ; } ) , lost . end ( ) ) ;
}
return result ;
}
inline ResourceId AssetsPreloader : : getId ( AssetType type , const std : : string & domain , const std : : string & key ) {
auto & typeTable = DKToId [ type ] ;
auto & domainTable = typeTable [ domain ] ;
if ( auto iter = domainTable . find ( key ) ; iter ! = domainTable . end ( ) )
return iter - > second ;
ensureNextId ( type ) ;
uint32_t id = NextId [ static_cast < size_t > ( type ) ] + + ;
domainTable [ key ] = id ;
return id ;
}
inline const AssetsPreloader : : MediaResource * AssetsPreloader : : getResource ( AssetType type , uint32_t id ) const {
auto iterType = MediaResources . find ( type ) ;
if ( iterType = = MediaResources . end ( ) )
return nullptr ;
auto iterRes = iterType - > second . find ( id ) ;
if ( iterRes = = iterType - > second . end ( ) )
return nullptr ;
return & iterRes - > second ;
}
inline std : : optional < std : : tuple < AssetType , uint32_t , const AssetsPreloader : : MediaResource * > > AssetsPreloader : : getResource ( const ResourceFile : : Hash_t & hash ) {
auto iter = HashToId . find ( hash ) ;
if ( iter = = HashToId . end ( ) )
return std : : nullopt ;
auto [ type , id ] = iter - > second ;
const MediaResource * res = getResource ( type , id ) ;
if ( ! res ) {
HashToId . erase ( iter ) ;
return std : : nullopt ;
}
if ( res - > Hash ! = hash ) {
HashToId . erase ( iter ) ;
return std : : nullopt ;
}
return std : : tuple < AssetType , uint32_t , const MediaResource * > { type , id , res } ;
}
inline std : : tuple < AssetsNodestate , std : : vector < AssetsModel > , std : : vector < AssetsTexture > >
AssetsPreloader : : getNodeDependency ( const std : : string & domain , const std : : string & key ) {
if ( domain = = " core " & & key = = " none " ) {
return { 0 , { } , { } } ;
}
AssetsNodestate nodestateId = getId ( AssetType : : Nodestate , domain , key + " .json " ) ;
const MediaResource * nodestate = getResource ( AssetType : : Nodestate , nodestateId ) ;
if ( ! nodestate )
return { nodestateId , { } , { } } ;
auto parsed = parseHeader ( nodestate - > Dependencies ) ;
if ( ! parsed )
return { nodestateId , { } , { } } ;
std : : vector < AssetsModel > models ;
std : : vector < AssetsTexture > textures ;
std : : unordered_set < AssetsModel > visited ;
std : : unordered_set < AssetsTexture > seenTextures ;
std : : function < void ( AssetsModel ) > visitModel = [ & ] ( AssetsModel modelId ) {
if ( ! visited . insert ( modelId ) . second )
return ;
models . push_back ( modelId ) ;
const MediaResource * modelRes = getResource ( AssetType : : Model , modelId ) ;
if ( ! modelRes )
return ;
auto header = parseHeader ( modelRes - > Dependencies ) ;
if ( ! header )
return ;
for ( uint32_t texId : header - > TextureDeps ) {
if ( seenTextures . insert ( texId ) . second )
textures . push_back ( texId ) ;
}
for ( uint32_t subId : header - > ModelDeps )
visitModel ( subId ) ;
} ;
for ( uint32_t modelId : parsed - > ModelDeps )
visitModel ( modelId ) ;
return { nodestateId , std : : move ( models ) , std : : move ( textures ) } ;
}
}