Compare commits
4 Commits
curle/ecs
...
curle/asse
Author | SHA1 | Date | |
---|---|---|---|
9fd733e8f4 | |||
49ee9fc10d | |||
b723a0778f | |||
5e60726a2b |
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
|
@ -58,8 +58,8 @@ jobs:
|
||||||
- name: Make output folder
|
- name: Make output folder
|
||||||
run: mkdir ./test-results
|
run: mkdir ./test-results
|
||||||
|
|
||||||
- name: Test shadow-file-format
|
- name: Test shadow-assets
|
||||||
run: ./bazel-bin/projs/shadow-file-format/test.exe -r junit -o ./test-results/shadow-file-format-test.xml
|
run: ./bazel-bin/projs/shadow-assets/test.exe -r junit -o ./test-results/shadow-assets-test.xml
|
||||||
|
|
||||||
- name: Publish Test Results
|
- name: Publish Test Results
|
||||||
uses: EnricoMi/publish-unit-test-result-action/composite@v1
|
uses: EnricoMi/publish-unit-test-result-action/composite@v1
|
||||||
|
|
35
imgui.ini
35
imgui.ini
|
@ -5,37 +5,36 @@ Collapsed=0
|
||||||
DockId=0x00000008,0
|
DockId=0x00000008,0
|
||||||
|
|
||||||
[Window][Dear ImGui Demo]
|
[Window][Dear ImGui Demo]
|
||||||
Pos=1193,178
|
Pos=1193,111
|
||||||
Size=62,25
|
Size=62,92
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000007,0
|
DockId=0x00000003,0
|
||||||
|
|
||||||
[Window][Game module window]
|
[Window][Game module window]
|
||||||
Pos=1193,8
|
Pos=956,275
|
||||||
Size=62,50
|
Size=302,121
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000004,0
|
|
||||||
|
|
||||||
[Window][Time]
|
[Window][Time]
|
||||||
Pos=1193,60
|
Pos=1193,8
|
||||||
Size=62,49
|
Size=62,101
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000005,0
|
DockId=0x00000002,0
|
||||||
|
|
||||||
[Window][Active Modules]
|
[Window][Active Modules]
|
||||||
Pos=1193,111
|
Pos=829,449
|
||||||
Size=62,65
|
Size=362,187
|
||||||
|
Collapsed=0
|
||||||
|
|
||||||
|
[Window][Game View]
|
||||||
|
Pos=60,60
|
||||||
|
Size=656,515
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000006,0
|
|
||||||
|
|
||||||
[Docking][Data]
|
[Docking][Data]
|
||||||
DockNode ID=0x00000001 Pos=1196,188 Size=379,195 Split=X
|
DockNode ID=0x00000001 Pos=1196,188 Size=379,195 Split=X
|
||||||
DockNode ID=0x00000008 Parent=0x00000001 SizeRef=652,419 HiddenTabBar=1 Selected=0x55954704
|
DockNode ID=0x00000008 Parent=0x00000001 SizeRef=652,419 HiddenTabBar=1 Selected=0x55954704
|
||||||
DockNode ID=0x00000009 Parent=0x00000001 SizeRef=129,419 Split=Y
|
DockNode ID=0x00000009 Parent=0x00000001 SizeRef=129,419 Split=Y
|
||||||
DockNode ID=0x00000002 Parent=0x00000009 SizeRef=219,34 Split=Y Selected=0xFC1D20C0
|
DockNode ID=0x00000002 Parent=0x00000009 SizeRef=219,34 Selected=0xE75A179E
|
||||||
DockNode ID=0x00000004 Parent=0x00000002 SizeRef=219,64 Selected=0xFC1D20C0
|
DockNode ID=0x00000003 Parent=0x00000009 SizeRef=219,31 Selected=0xE87781F4
|
||||||
DockNode ID=0x00000005 Parent=0x00000002 SizeRef=219,62 Selected=0xE75A179E
|
|
||||||
DockNode ID=0x00000003 Parent=0x00000009 SizeRef=219,31 Split=Y Selected=0xEE305C78
|
|
||||||
DockNode ID=0x00000006 Parent=0x00000003 SizeRef=219,142 Selected=0xEE305C78
|
|
||||||
DockNode ID=0x00000007 Parent=0x00000003 SizeRef=219,55 Selected=0xE87781F4
|
|
||||||
|
|
||||||
|
|
|
@ -7,17 +7,17 @@ set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
||||||
|
|
||||||
FILE(GLOB_RECURSE SOURCES
|
FILE(GLOB_RECURSE SOURCES
|
||||||
core/src/*.cpp
|
core/src/*.cpp
|
||||||
shadow-entity/src/*.cpp
|
|
||||||
shadow-renderer/src/*.cpp
|
shadow-renderer/src/*.cpp
|
||||||
shadow-reflection/src/*.cpp
|
shadow-reflection/src/*.cpp
|
||||||
shadow-utility/src/*.cpp
|
shadow-utility/src/*.cpp
|
||||||
|
shadow-assets/src/*.cpp
|
||||||
)
|
)
|
||||||
FILE(GLOB_RECURSE HEADERS
|
FILE(GLOB_RECURSE HEADERS
|
||||||
core/inc/*.h
|
core/inc/*.h
|
||||||
shadow-entity/inc/*.h
|
|
||||||
shadow-renderer/inc/*.h
|
shadow-renderer/inc/*.h
|
||||||
shadow-reflection/inc/*.h
|
shadow-reflection/inc/*.h
|
||||||
shadow-utility/inc/*.h
|
shadow-utility/inc/*.h
|
||||||
|
shadow-assets/src/*.h
|
||||||
)
|
)
|
||||||
|
|
||||||
add_library(shadow-engine SHARED ${SOURCES} $<TARGET_OBJECTS:imgui>)
|
add_library(shadow-engine SHARED ${SOURCES} $<TARGET_OBJECTS:imgui>)
|
||||||
|
@ -26,10 +26,10 @@ target_include_directories(shadow-engine
|
||||||
PRIVATE ${SDL2_INCLUDE_DIRS}
|
PRIVATE ${SDL2_INCLUDE_DIRS}
|
||||||
PUBLIC
|
PUBLIC
|
||||||
core/inc
|
core/inc
|
||||||
shadow-entity/inc
|
|
||||||
shadow-renderer/inc
|
shadow-renderer/inc
|
||||||
shadow-reflection/inc
|
shadow-reflection/inc
|
||||||
shadow-utility/inc
|
shadow-utility/inc
|
||||||
|
shadow-assets/src
|
||||||
${glm_SOURCE_DIR}
|
${glm_SOURCE_DIR}
|
||||||
INTERFACE
|
INTERFACE
|
||||||
${imgui_SOURCE_DIR}
|
${imgui_SOURCE_DIR}
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstring>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
// ShadowEntity temporary namespace
|
|
||||||
namespace SE {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Universally Unique ID.
|
|
||||||
* 128 Bits.
|
|
||||||
*
|
|
||||||
* Unique per runtime only - the suitability for serialization is undetermined.
|
|
||||||
*/
|
|
||||||
|
|
||||||
class UUID {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Data storage; 128 bits.
|
|
||||||
* 2 x 64 bit
|
|
||||||
* 4 x 32 bit
|
|
||||||
* 16 x 8 bit
|
|
||||||
*/
|
|
||||||
|
|
||||||
union Data {
|
|
||||||
uint64_t u64[2];
|
|
||||||
uint32_t u32[4];
|
|
||||||
uint8_t u8[16];
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
|
||||||
// Create a new, unused, UUID.
|
|
||||||
static UUID Generate();
|
|
||||||
// Check whether the UUID is correctly formed.
|
|
||||||
static bool IsValidStr(char const* str);
|
|
||||||
|
|
||||||
// Create an empty UUID.
|
|
||||||
inline UUID() { std::memset(&data.u8, 0, 16); }
|
|
||||||
// Create a UUID based on the given values.
|
|
||||||
inline UUID(uint64_t i0, uint64_t i1) { data.u64[0] = i0; data.u64[1] = i1; }
|
|
||||||
inline UUID(uint32_t i0, uint32_t i1, uint32_t i2, uint32_t i3) { data.u32[0] = i0; data.u32[1] = i1; data.u32[2] = i2; data.u32[3] = i3; }
|
|
||||||
inline explicit UUID(std::string const& str) : UUID(str.c_str()) {}
|
|
||||||
// Create a UUID from the given format.
|
|
||||||
explicit UUID(char const* str);
|
|
||||||
|
|
||||||
// Check whether the UUID is nonzero.
|
|
||||||
inline bool IsValid() const { return data.u64[0] != 0 && data.u64[1] != 0; }
|
|
||||||
// Set the UUID to zero.
|
|
||||||
inline void Clear() { std::memset(&data.u8, 0, 16); }
|
|
||||||
|
|
||||||
// Get a section of the UUID's data as the given bit width.
|
|
||||||
inline uint8_t GetU8(size_t idx) const { return data.u8[idx]; }
|
|
||||||
inline uint32_t GetU32(size_t idx) const { return data.u32[idx]; }
|
|
||||||
inline uint64_t GetU64(size_t idx) const { return data.u64[idx]; }
|
|
||||||
|
|
||||||
// Check whether this and a given UUID are in/equal.
|
|
||||||
__inline bool operator==(UUID const& other) const { return data.u64[0] == other.data.u64[0] && data.u64[1] == other.data.u64[1]; }
|
|
||||||
__inline bool operator!=(UUID const& other) const { return !(*this == other); }
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
Data data {};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include <math/transform.h>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A temporary header that contains some of the bounds implementations.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Math {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A bounding box that can be rotated freely.
|
|
||||||
* Can be used as the collision box for an entity.
|
|
||||||
*/
|
|
||||||
struct OrientedBB {
|
|
||||||
|
|
||||||
OrientedBB() = default;
|
|
||||||
|
|
||||||
|
|
||||||
Quaternion orientation;
|
|
||||||
Vector center;
|
|
||||||
Vector extent;
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A temporary header that contains some of the core transform logic.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <xmmintrin.h>
|
|
||||||
|
|
||||||
namespace Math {
|
|
||||||
|
|
||||||
struct alignas(16) Vector {
|
|
||||||
Vector() = default;
|
|
||||||
|
|
||||||
union {
|
|
||||||
struct { float x, y, z, w; };
|
|
||||||
__m128 data;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct alignas(16) Quaternion {
|
|
||||||
|
|
||||||
inline Quaternion() = default;
|
|
||||||
|
|
||||||
union {
|
|
||||||
struct { float x, y, z, w; };
|
|
||||||
__m128 data;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
class Transform {
|
|
||||||
public:
|
|
||||||
Transform() = default;
|
|
||||||
|
|
||||||
const Vector& GetTranslation() const { return translation; }
|
|
||||||
const Quaternion& GetRotation() const { return rotation; }
|
|
||||||
|
|
||||||
inline Vector GetRightVector() const;
|
|
||||||
inline Vector GetForwardVector() const;
|
|
||||||
inline Vector GetUpVector() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
Quaternion rotation;
|
|
||||||
Vector translation;
|
|
||||||
Vector scale;
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,56 +0,0 @@
|
||||||
#include <id/UUID.h>
|
|
||||||
|
|
||||||
namespace SE {
|
|
||||||
static_assert(sizeof(UUID) == 16, "UUID has incorrect size");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verify that a string has the correct format;
|
|
||||||
* XXXXXXXX-XXXX-XXX-XXXXX-XXXXXXXXXXXX
|
|
||||||
*
|
|
||||||
* The length must be 36.
|
|
||||||
* There must be dashes at index 8, 13, 18 and 23.
|
|
||||||
* @param str the input string
|
|
||||||
* @return whether the UUID string is correctly formed
|
|
||||||
*/
|
|
||||||
bool UUID::IsValidStr(const char *str) {
|
|
||||||
size_t const len = strlen(str);
|
|
||||||
if (len != 36) return false;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < len; i++) {
|
|
||||||
char c = str[i];
|
|
||||||
if (c == '-') {
|
|
||||||
if (i != 8 && i != 13 && i != 18 && i != 23) return false;
|
|
||||||
} else if (! std::isxdigit(c)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
UUID::UUID(char const* str ) {
|
|
||||||
// A single byte is two hex characters.
|
|
||||||
// Store them here so that we can use them later.
|
|
||||||
char c0 = '\0', c1;
|
|
||||||
|
|
||||||
size_t const len = strlen( str );
|
|
||||||
uint32_t byteIdx = 0;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < len; i++ ) {
|
|
||||||
char const c = str[i];
|
|
||||||
if ( c == '-' )
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Scan for pairs of characters.
|
|
||||||
// Only assign a byte if two have been parsed.
|
|
||||||
if (c0 == '\0') {
|
|
||||||
c0 = c;
|
|
||||||
} else {
|
|
||||||
c1 = c;
|
|
||||||
data.u8[byteIdx++] = std::stoi(std::string(c0, c1));
|
|
||||||
// Reset the first char so that we can return to scanning a pair.
|
|
||||||
c0 = '\0';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
265
projs/shadow/shadow-engine/shadow-assets/src/fs/file.cpp
Normal file
265
projs/shadow/shadow-engine/shadow-assets/src/fs/file.cpp
Normal file
|
@ -0,0 +1,265 @@
|
||||||
|
#include <fs/file.h>
|
||||||
|
#include <vector>
|
||||||
|
#include "management/synchronization.h"
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fs/path.h>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
namespace ShadowEngine {
|
||||||
|
|
||||||
|
// Because fuck Linux? Need platform-specific source files!
|
||||||
|
#ifdef _WIN32
|
||||||
|
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#include <Windows.h>
|
||||||
|
|
||||||
|
FileInput::FileInput() {
|
||||||
|
handle = (void*) INVALID_HANDLE_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileOutput::FileOutput() {
|
||||||
|
error = false;
|
||||||
|
handle = (void*) INVALID_HANDLE_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileOutput::open(std::string& path) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An async operation to be performed.
|
||||||
|
* For reading files from disk into memory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct AsyncRead {
|
||||||
|
enum class Flags : uint32_t {
|
||||||
|
FAILED = 0, // The read failed due to some error.
|
||||||
|
CANCELLED // The read was cancelled due to the resource not being needed any more.
|
||||||
|
};
|
||||||
|
|
||||||
|
AsyncRead() : data() {}
|
||||||
|
|
||||||
|
bool isFailed() const { return flags == Flags::FAILED; }
|
||||||
|
bool isCancelled() const { return flags == Flags::CANCELLED; }
|
||||||
|
|
||||||
|
FileSystem::ContentCallback callback;
|
||||||
|
OutputMemoryStream data;
|
||||||
|
std::string path;
|
||||||
|
uint32_t id = 0;
|
||||||
|
Flags flags;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The FileSystem that operates on raw on-disk files.
|
||||||
|
struct DiskFS;
|
||||||
|
|
||||||
|
struct DiskFS : FileSystem {
|
||||||
|
|
||||||
|
explicit DiskFS(std::string& path) : sem(0, 0xffff) {
|
||||||
|
setBasePath(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasWork() override {
|
||||||
|
return workCounter != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string const& getBasePath() const override { return basePath; }
|
||||||
|
void setBasePath(std::string& path) final {
|
||||||
|
basePath = Path::normalise(path);
|
||||||
|
if (!basePath.ends_with('/') && !basePath.ends_with('\\'))
|
||||||
|
basePath.append("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool saveSync(const Path& path, const uint8_t* data, const size_t size) override {
|
||||||
|
FileOutput file;
|
||||||
|
std::string fullPath(basePath.append(path.c_str()));
|
||||||
|
|
||||||
|
if (!file.open(fullPath)) return false;
|
||||||
|
bool res = file.write(data, size);
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool readSync(const Path& path, struct OutputMemoryStream& content) override {
|
||||||
|
FileInput file;
|
||||||
|
std::string fullPath(basePath.append(path.c_str()));
|
||||||
|
|
||||||
|
if (!file.open(fullPath)) return false;
|
||||||
|
|
||||||
|
content.resize(file.size());
|
||||||
|
if (!file.read(content.dataMut(), content.size())) {
|
||||||
|
file.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
file.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncHandle readAsync(const Path& file, const ContentCallback& callback) override {
|
||||||
|
if (!file.isEmpty()) return AsyncHandle::invalid();
|
||||||
|
|
||||||
|
MutexGuard lock(mutex);
|
||||||
|
workCounter++;
|
||||||
|
|
||||||
|
AsyncRead& read = queue.emplace_back();
|
||||||
|
if (++lastID == 0) lastID++;
|
||||||
|
|
||||||
|
read.id = lastID;
|
||||||
|
read.path = file.c_str();
|
||||||
|
read.callback = callback;
|
||||||
|
sem.raise();
|
||||||
|
|
||||||
|
return AsyncHandle(read.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void cancelAsync(AsyncHandle& handle) override {
|
||||||
|
MutexGuard lock(mutex);
|
||||||
|
|
||||||
|
for (AsyncRead& read : queue) {
|
||||||
|
if (read.id == handle.value) {
|
||||||
|
read.flags = AsyncRead::Flags::CANCELLED;
|
||||||
|
workCounter--;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (AsyncRead& read : finished) {
|
||||||
|
if (read.id == handle.value) {
|
||||||
|
read.flags = AsyncRead::Flags::CANCELLED;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool open(std::string& path, FileInput& file) override {
|
||||||
|
return file.open(basePath.append(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool open(std::string& path, FileOutput& file) override {
|
||||||
|
return file.open(basePath.append(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool deleteFile(std::string& path) override {
|
||||||
|
return std::remove((basePath.append(path).c_str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool moveFile(std::string& from, std::string& to) override {
|
||||||
|
try {
|
||||||
|
std::rename(basePath.append(from).c_str(), basePath.append(to).c_str());
|
||||||
|
} catch (std::filesystem::filesystem_error& e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool copyFile(std::string& from, std::string& to) override {
|
||||||
|
try {
|
||||||
|
std::filesystem::copy(basePath.append(from).c_str(), basePath.append(to).c_str());
|
||||||
|
} catch (std::filesystem::filesystem_error& e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fileExists(std::string& path) override {
|
||||||
|
return std::filesystem::exists(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t getLastModified(std::string& path) override {
|
||||||
|
return std::filesystem::last_write_time(path).time_since_epoch().count();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: File iterators
|
||||||
|
|
||||||
|
void processCallbacks() override {
|
||||||
|
// TODO: Timeout this function!
|
||||||
|
for (;;) {
|
||||||
|
mutex.enter();
|
||||||
|
if (finished.empty() || workCounter == 0) {
|
||||||
|
mutex.exit();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncRead item = finished[0];
|
||||||
|
finished.erase(finished.begin());
|
||||||
|
--workCounter;
|
||||||
|
|
||||||
|
mutex.exit();
|
||||||
|
|
||||||
|
if (!item.isCancelled())
|
||||||
|
item.callback.invoke(item.data.size(), (const uint8_t*) item.data.data(), !item.isFailed());
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Task Management
|
||||||
|
std::string basePath;
|
||||||
|
std::vector<AsyncRead> queue;
|
||||||
|
uint64_t workCounter;
|
||||||
|
std::vector<AsyncRead> finished;
|
||||||
|
Mutex mutex;
|
||||||
|
Semaphore sem;
|
||||||
|
|
||||||
|
uint32_t lastID;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VFS : DiskFS {
|
||||||
|
VFS(std::string& root_pack_path) : DiskFS((std::string &) "vfs:/") {
|
||||||
|
if (!pack.open(root_pack_path)) {
|
||||||
|
spdlog::error("Unable to open " + root_pack_path + ", please check paths");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto count = pack.read<size_t>();
|
||||||
|
for (size_t i = 0; i < count; i++) {
|
||||||
|
const auto hash = pack.read<PathHash>();
|
||||||
|
PackFile& file = packFiles[hash];
|
||||||
|
file.offset = pack.read<size_t>();
|
||||||
|
file.size = pack.read<size_t>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~VFS() { pack.close(); }
|
||||||
|
|
||||||
|
bool readSync(const Path& path, OutputMemoryStream& content) override {
|
||||||
|
std::string basename = Path::getFilename(const_cast<std::string &>(path.get()));
|
||||||
|
PathHash hash = path.getHash();
|
||||||
|
|
||||||
|
auto i = packFiles.find(hash);
|
||||||
|
if (i == packFiles.end()) return false;
|
||||||
|
|
||||||
|
content.resize(i->second.size);
|
||||||
|
MutexGuard lock(mutex);
|
||||||
|
|
||||||
|
const size_t headerSize = sizeof(uint32_t) + packFiles.size() * (3 * sizeof(size_t));
|
||||||
|
if (pack.seek(i->second.offset + headerSize) || !pack.read(content.dataMut(), content.size())) {
|
||||||
|
spdlog::error("Could not read file " + path.get() + " from the pack file.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PackFile {
|
||||||
|
size_t offset;
|
||||||
|
size_t size;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::map<PathHash, PackFile> packFiles;
|
||||||
|
FileInput pack;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<FileSystem> FileSystem::createDiskFS(std::string &basePath) {
|
||||||
|
return std::make_unique<DiskFS>(basePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<FileSystem> FileSystem::createVFS(std::string& basePath) {
|
||||||
|
return std::make_unique<VFS>(basePath);
|
||||||
|
}
|
||||||
|
}
|
115
projs/shadow/shadow-engine/shadow-assets/src/fs/file.h
Normal file
115
projs/shadow/shadow-engine/shadow-assets/src/fs/file.h
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
#pragma once
|
||||||
|
#include <fs/iostream.h>
|
||||||
|
#include <fs/path.h>
|
||||||
|
#include <management/delegate.h>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
template <class T> struct Delegate;
|
||||||
|
|
||||||
|
namespace ShadowEngine {
|
||||||
|
|
||||||
|
// An input stream that can read a file on disk.
|
||||||
|
struct FileInput final : InputStream {
|
||||||
|
FileInput();
|
||||||
|
~FileInput() = default;
|
||||||
|
|
||||||
|
[[nodiscard]] bool open(std::string& path);
|
||||||
|
void close();
|
||||||
|
|
||||||
|
using InputStream::read;
|
||||||
|
[[nodiscard]] bool read(void* data, size_t size) override;
|
||||||
|
const void* getBuffer() const override { return nullptr; }
|
||||||
|
|
||||||
|
size_t size() const override;
|
||||||
|
size_t pos();
|
||||||
|
|
||||||
|
[[nodiscard]] bool seek(size_t pos);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void* handle;
|
||||||
|
};
|
||||||
|
|
||||||
|
// An output stream that can write to a file on disk.
|
||||||
|
struct FileOutput final : OutputStream {
|
||||||
|
FileOutput();
|
||||||
|
~FileOutput() = default;
|
||||||
|
|
||||||
|
[[nodiscard]] bool open(std::string& path);
|
||||||
|
void close();
|
||||||
|
void flush();
|
||||||
|
bool errored() const { return error; }
|
||||||
|
using OutputStream::write;
|
||||||
|
[[nodiscard]] bool write(const void* data, size_t size) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
FileOutput(const FileOutput&) = delete;
|
||||||
|
void* handle;
|
||||||
|
bool error;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FileInfo {
|
||||||
|
bool directory;
|
||||||
|
std::string filename;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A generic Filesystem API.
|
||||||
|
* Allows interacting with files on disk the same as files in our Virtual Package Format.
|
||||||
|
*/
|
||||||
|
struct FileSystem {
|
||||||
|
// A function called when the data of a file is updated, such as when an asynchronous operation completes.
|
||||||
|
using ContentCallback = Delegate<void(size_t, const uint8_t*, bool)>;
|
||||||
|
// A handle for asynchronous data movement; such as reading or writing a file.
|
||||||
|
struct AsyncHandle {
|
||||||
|
static AsyncHandle invalid() { return AsyncHandle(0xffffffff); }
|
||||||
|
explicit AsyncHandle(uint32_t val) : value(val) {}
|
||||||
|
|
||||||
|
[[nodiscard]] bool valid() const { return value != 0xffffffff; }
|
||||||
|
|
||||||
|
uint32_t value;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a Filesystem that interacts with files on disk.
|
||||||
|
static std::unique_ptr<FileSystem> createDiskFS(std::string& basePath);
|
||||||
|
// Create a Virtual Filesystem based on the given path.
|
||||||
|
static std::unique_ptr<FileSystem> createVFS(std::string& basePath);
|
||||||
|
|
||||||
|
virtual ~FileSystem() = default;
|
||||||
|
|
||||||
|
// Open a file for reading.
|
||||||
|
virtual bool open(std::string& path, FileInput& input) = 0;
|
||||||
|
// Open a file for writing.
|
||||||
|
virtual bool open(std::string& path, FileOutput& output) = 0;
|
||||||
|
// Check whether a file exists at the given path.
|
||||||
|
virtual bool fileExists(std::string& path) = 0;
|
||||||
|
// Get the time a file at the given path was last modified.
|
||||||
|
virtual size_t getLastModified(std::string& path) = 0;
|
||||||
|
// Copy a file from one path to another.
|
||||||
|
virtual bool copyFile(std::string& from, std::string& to) = 0;
|
||||||
|
// Move a file from one path to another.
|
||||||
|
virtual bool moveFile(std::string& from, std::string& to) = 0;
|
||||||
|
// Disassociate any files at the given path (not an immediate delete)
|
||||||
|
virtual bool deleteFile(std::string& path) = 0;
|
||||||
|
|
||||||
|
// Get the path that this FileSystem originates at. The default is "/" for VFS, and whatever the Executable Path is for Disk FS.
|
||||||
|
virtual std::string const& getBasePath() const = 0;
|
||||||
|
// Set a new base path for the FileSystem. Any operations involving file paths will be relative to this new path.
|
||||||
|
virtual void setBasePath(std::string& path) = 0;
|
||||||
|
|
||||||
|
// Process all the callbacks for async file operations.
|
||||||
|
virtual void processCallbacks() = 0;
|
||||||
|
// Check whether there are any outstanding async operations that need work.
|
||||||
|
virtual bool hasWork() = 0;
|
||||||
|
|
||||||
|
// Write new content to a file synchronously. The thread will be blocked when doing this.
|
||||||
|
virtual bool saveSync(const Path& file, const uint8_t* content, size_t size) = 0;
|
||||||
|
// Read content from a file synchronously. The thread will be blocked when doing this.
|
||||||
|
virtual bool readSync(const Path& file, struct OutputMemoryStream& content) = 0;
|
||||||
|
|
||||||
|
// Read a file asynchronously. The given callback will be called with the file content once it is available.
|
||||||
|
virtual AsyncHandle readAsync(const Path& file, const ContentCallback& callback) = 0;
|
||||||
|
// Cancel an asynchronous operation, if it is not already complete. The associated callback will be called with a special flag.
|
||||||
|
virtual void cancelAsync(AsyncHandle& handle) = 0;
|
||||||
|
};
|
||||||
|
}
|
148
projs/shadow/shadow-engine/shadow-assets/src/fs/hash.cpp
Normal file
148
projs/shadow/shadow-engine/shadow-assets/src/fs/hash.cpp
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
#include <fs/hash.h>
|
||||||
|
#include "xxhash.h"
|
||||||
|
|
||||||
|
namespace ShadowEngine {
|
||||||
|
|
||||||
|
StableHash::StableHash(const void *data, uint32_t length) {
|
||||||
|
hash = XXHash64::hash(data, length, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
StableHash::StableHash(std::string &str) {
|
||||||
|
hash = XXHash64::hash(str.data(), str.size(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
HeapHash::HeapHash(const void *data, uint32_t length) {
|
||||||
|
hash = XXHash64::hash(data, length, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
HeapHash::HeapHash(std::string &str) {
|
||||||
|
hash = XXHash64::hash(str.data(), str.size(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
HeapHash HeapHash::fromLong(size_t hash) {
|
||||||
|
HeapHash heap;
|
||||||
|
heap.hash = hash;
|
||||||
|
return heap;
|
||||||
|
}
|
||||||
|
|
||||||
|
HeapHash32 HeapHash32::fromInt(uint32_t hash) {
|
||||||
|
HeapHash32 heap;
|
||||||
|
heap.hash = hash;
|
||||||
|
return heap;
|
||||||
|
}
|
||||||
|
|
||||||
|
StableHash StableHash::fromLong(size_t hash) {
|
||||||
|
StableHash stable;
|
||||||
|
stable.hash = hash;
|
||||||
|
return stable;
|
||||||
|
}
|
||||||
|
|
||||||
|
StableHash32 StableHash32::fromInt(uint32_t hash) {
|
||||||
|
StableHash32 stable;
|
||||||
|
stable.hash = hash;
|
||||||
|
return stable;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t CRC[256] = {
|
||||||
|
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
|
||||||
|
0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
|
||||||
|
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
|
||||||
|
0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
|
||||||
|
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
|
||||||
|
0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
|
||||||
|
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
|
||||||
|
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
|
||||||
|
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
|
||||||
|
0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
|
||||||
|
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
|
||||||
|
0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
|
||||||
|
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
|
||||||
|
0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
|
||||||
|
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
|
||||||
|
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
|
||||||
|
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
|
||||||
|
0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
|
||||||
|
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
|
||||||
|
0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
|
||||||
|
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
|
||||||
|
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
|
||||||
|
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
|
||||||
|
0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
|
||||||
|
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
|
||||||
|
0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
|
||||||
|
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
|
||||||
|
0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
|
||||||
|
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
|
||||||
|
0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
|
||||||
|
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
|
||||||
|
0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
|
||||||
|
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
|
||||||
|
0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
|
||||||
|
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
|
||||||
|
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
|
||||||
|
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
|
||||||
|
0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
|
||||||
|
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
|
||||||
|
0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
|
||||||
|
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
|
||||||
|
0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
|
||||||
|
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d};
|
||||||
|
|
||||||
|
static uint32_t CRC32(const void* data, uint32_t length) {
|
||||||
|
const auto* c = static_cast<const uint8_t*>(data);
|
||||||
|
uint32_t crcTemp = 0xFFFFFFFF;
|
||||||
|
uint32_t len = length;
|
||||||
|
while (len) {
|
||||||
|
crcTemp = (crcTemp >> 8) ^ CRC[(crcTemp & 0xFF) ^ *c];
|
||||||
|
--len; ++c;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ~crcTemp;
|
||||||
|
}
|
||||||
|
|
||||||
|
StableHash32::StableHash32(const void *data, uint32_t length) {
|
||||||
|
hash = CRC32(data, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
StableHash32::StableHash32(std::string &str) {
|
||||||
|
hash = CRC32(str.data(), str.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
static XXHash64 DeferredHashState(0);
|
||||||
|
|
||||||
|
DeferredHash::DeferredHash() {
|
||||||
|
DeferredHashState = XXHash64(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeferredHash::insert(const void *data, uint32_t length) {
|
||||||
|
DeferredHashState.add(data, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
StableHash32 DeferredHash::submit32() {
|
||||||
|
const auto result = DeferredHashState.hash();
|
||||||
|
return StableHash32::fromInt(uint32_t(result ^ (result >> 32)));
|
||||||
|
}
|
||||||
|
|
||||||
|
StableHash DeferredHash::submit() {
|
||||||
|
const auto result = DeferredHashState.hash();
|
||||||
|
return StableHash::fromLong(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
DeferredHeapHash::DeferredHeapHash() {
|
||||||
|
DeferredHashState = XXHash64(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeferredHeapHash::insert(const void *data, uint32_t length) {
|
||||||
|
DeferredHashState.add(data, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
HeapHash32 DeferredHeapHash::submit32() {
|
||||||
|
const auto result = DeferredHashState.hash();
|
||||||
|
return HeapHash32::fromInt(uint32_t(result ^ (result >> 32)));
|
||||||
|
}
|
||||||
|
|
||||||
|
HeapHash DeferredHeapHash::submit() {
|
||||||
|
const auto result = DeferredHashState.hash();
|
||||||
|
return HeapHash::fromLong(result);
|
||||||
|
}
|
||||||
|
}
|
158
projs/shadow/shadow-engine/shadow-assets/src/fs/hash.h
Normal file
158
projs/shadow/shadow-engine/shadow-assets/src/fs/hash.h
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace ShadowEngine {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A 64-bit hashing algorithm that uses the state of the allocation heap as a "salt".
|
||||||
|
* Outputs are NOT stable, so do not serialize this.
|
||||||
|
* However, because it uses the heap, it has a very low collision rate.
|
||||||
|
*/
|
||||||
|
struct HeapHash {
|
||||||
|
// For if you MUST recreate a hash exactly.
|
||||||
|
// Please only use this for testing.
|
||||||
|
static HeapHash fromLong(size_t hash);
|
||||||
|
|
||||||
|
HeapHash() = default;
|
||||||
|
// Hash a string; for paths and such.
|
||||||
|
explicit HeapHash(std::string& str);
|
||||||
|
// Hash arbitrary data.
|
||||||
|
HeapHash(const void* data, uint32_t length);
|
||||||
|
|
||||||
|
bool operator!= (const HeapHash& other) const { return hash != other.hash; }
|
||||||
|
bool operator== (const HeapHash& other) const { return hash == other.hash; }
|
||||||
|
|
||||||
|
size_t getHash() const { return hash; }
|
||||||
|
private:
|
||||||
|
size_t hash = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A 32-bit hashing algorithm that uses the state of the allocation heap as a "salt".
|
||||||
|
* Outputs are NOT stable, so do not serialize this.
|
||||||
|
* However, because it uses the heap, it has a very low collision rate.
|
||||||
|
*/
|
||||||
|
struct HeapHash32 {
|
||||||
|
// For if you MUST recreate a hash exactly.
|
||||||
|
// Please only use this for testing.
|
||||||
|
static HeapHash32 fromInt(uint32_t hash);
|
||||||
|
|
||||||
|
HeapHash32() = default;
|
||||||
|
// Hash a string; for paths and such.
|
||||||
|
explicit HeapHash32(std::string& str);
|
||||||
|
// Hash arbitrary data.
|
||||||
|
HeapHash32(const void* data, uint32_t length);
|
||||||
|
|
||||||
|
bool operator!= (HeapHash32& other) const { return hash != other.hash; }
|
||||||
|
bool operator== (HeapHash32& other) const { return hash == other.hash; }
|
||||||
|
|
||||||
|
uint32_t getHash() const { return hash; }
|
||||||
|
private:
|
||||||
|
uint32_t hash = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A 64-bit hashing algorithm that generates the same hash value per input every time.
|
||||||
|
* A little more likely to generate conflicts than the hash that uses the state of the heap as a salt.
|
||||||
|
* Suitable for serialization.
|
||||||
|
*/
|
||||||
|
struct StableHash {
|
||||||
|
static StableHash fromLong(size_t data);
|
||||||
|
StableHash() = default;
|
||||||
|
explicit StableHash(std::string& str);
|
||||||
|
StableHash(const void* data, uint32_t length);
|
||||||
|
|
||||||
|
bool operator!= (const StableHash& other) const { return hash != other.hash; }
|
||||||
|
bool operator== (const StableHash& other) const { return hash == other.hash; }
|
||||||
|
bool operator< (const StableHash& other) const { return hash < other.hash; }
|
||||||
|
|
||||||
|
[[nodiscard]] size_t getHash() const { return hash; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
size_t hash = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A 32-bit hashing algorithm that generates the same hash value per input every time.
|
||||||
|
* A little more likely to generate conflicts than the hash that uses the state of the heap as a salt.
|
||||||
|
* Suitable for serialization.
|
||||||
|
*/
|
||||||
|
struct StableHash32 {
|
||||||
|
static StableHash32 fromInt(uint32_t data);
|
||||||
|
StableHash32() = default;
|
||||||
|
StableHash32(std::string& str);
|
||||||
|
StableHash32(const void* data, uint32_t length);
|
||||||
|
|
||||||
|
bool operator!= (StableHash32& other) const { return hash != other.hash; }
|
||||||
|
bool operator== (StableHash32& other) const { return hash == other.hash; }
|
||||||
|
bool operator< (StableHash32& other) const { return hash < other.hash; }
|
||||||
|
|
||||||
|
uint32_t getHash() const { return hash; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint32_t hash = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// File Paths are hashed using the 64-bit StableHash system.
|
||||||
|
using PathHash = StableHash;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A hashing utility that lets you insert data piecemeal before committing to the hash.
|
||||||
|
* Useful for when you're parsing a file and need to wait for more data to be available before hashing.
|
||||||
|
* Generates a Stable Hash.
|
||||||
|
*/
|
||||||
|
struct DeferredHash {
|
||||||
|
DeferredHash();
|
||||||
|
// Insert new data to be considered for hashing
|
||||||
|
void insert(const void* data, uint32_t length);
|
||||||
|
// Submit the data to the hashing algorithm, and return a value in 64-bit StableHash
|
||||||
|
StableHash submit();
|
||||||
|
// Submit the data to the hashing algorithm, and return a value in 32-bit StableHash
|
||||||
|
StableHash32 submit32();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A hashing utility that lets you insert data piecemeal before committing to the hash.
|
||||||
|
* Useful for when you're parsing a file and need to wait for more data to be available before hashing.
|
||||||
|
* Generates a Heap Hash.
|
||||||
|
*/
|
||||||
|
struct DeferredHeapHash {
|
||||||
|
DeferredHeapHash();
|
||||||
|
// Insert new data to be considered for hashing
|
||||||
|
void insert(const void* data, uint32_t length);
|
||||||
|
// Submit the data to the hashing algorithm, and return a value in 64-bit HeapHash
|
||||||
|
HeapHash submit();
|
||||||
|
// Submit the data to the hashing algorithm, and return a value in 32-bit HeapHash
|
||||||
|
HeapHash32 submit32();
|
||||||
|
};
|
||||||
|
|
||||||
|
/** The implementations of these hashing algorithms */
|
||||||
|
|
||||||
|
template <class Hash> struct HashFunc;
|
||||||
|
|
||||||
|
template<> struct HashFunc<HeapHash> {
|
||||||
|
static uint32_t get(const HeapHash& h) {
|
||||||
|
const size_t hash = h.getHash();
|
||||||
|
return uint32_t(hash & (hash >> 16));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<> struct HashFunc<StableHash> {
|
||||||
|
static uint32_t get(const StableHash& h) {
|
||||||
|
const size_t hash = h.getHash();
|
||||||
|
return uint32_t(hash & (hash >> 16));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<> struct HashFunc<HeapHash32> {
|
||||||
|
static uint32_t get(const HeapHash32& h) {
|
||||||
|
return h.getHash();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<> struct HashFunc<StableHash32> {
|
||||||
|
static uint32_t get(const StableHash& h) {
|
||||||
|
return h.getHash();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
216
projs/shadow/shadow-engine/shadow-assets/src/fs/iostream.cpp
Normal file
216
projs/shadow/shadow-engine/shadow-assets/src/fs/iostream.cpp
Normal file
|
@ -0,0 +1,216 @@
|
||||||
|
#include <fs/iostream.h>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace ShadowEngine {
|
||||||
|
|
||||||
|
OutputMemoryStream::OutputMemoryStream(void *data, size_t size)
|
||||||
|
: buffer(static_cast<uint8_t *>(data)), capacity(size), usage(0) {}
|
||||||
|
|
||||||
|
OutputMemoryStream::OutputMemoryStream(ShadowEngine::OutputMemoryStream &&str) noexcept {
|
||||||
|
capacity = str.capacity;
|
||||||
|
buffer = str.buffer;
|
||||||
|
usage = str.usage;
|
||||||
|
|
||||||
|
str.free();
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputMemoryStream& OutputMemoryStream::operator=(ShadowEngine::OutputMemoryStream &&str) noexcept {
|
||||||
|
capacity = str.capacity;
|
||||||
|
buffer = str.buffer;
|
||||||
|
usage = str.usage;
|
||||||
|
|
||||||
|
str.free();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputMemoryStream& OutputMemoryStream::operator=(const ShadowEngine::OutputMemoryStream &rhs) noexcept {
|
||||||
|
usage = rhs.usage;
|
||||||
|
|
||||||
|
if (rhs.capacity > 0) {
|
||||||
|
buffer = (uint8_t*)malloc(rhs.capacity);
|
||||||
|
memcpy(buffer, rhs.buffer, rhs.capacity);
|
||||||
|
capacity = rhs.capacity;
|
||||||
|
} else {
|
||||||
|
buffer = nullptr;
|
||||||
|
capacity = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputMemoryStream::OutputMemoryStream(const ShadowEngine::OutputMemoryStream &rhs) noexcept {
|
||||||
|
usage = rhs.usage;
|
||||||
|
|
||||||
|
if (rhs.capacity > 0) {
|
||||||
|
buffer = (uint8_t*)malloc(rhs.capacity);
|
||||||
|
memcpy(buffer, rhs.buffer, rhs.capacity);
|
||||||
|
capacity = rhs.capacity;
|
||||||
|
} else {
|
||||||
|
buffer = nullptr;
|
||||||
|
capacity = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
OutputMemoryStream::~OutputMemoryStream() = default;
|
||||||
|
|
||||||
|
OutputStream &OutputStream::operator<<(std::string &str) {
|
||||||
|
write(str.data(), str.length());
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputStream &OutputStream::operator<<(const char* str) {
|
||||||
|
write(str, strlen(str));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputStream &OutputStream::operator<<(uint32_t val) {
|
||||||
|
std::string str = std::to_string(val);
|
||||||
|
write(str.c_str(), str.length());
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputStream &OutputStream::operator<<(int32_t val) {
|
||||||
|
std::string str = std::to_string(val);
|
||||||
|
write(str.c_str(), str.length());
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputStream &OutputStream::operator<<(uint64_t val) {
|
||||||
|
std::string str = std::to_string(val);
|
||||||
|
write(str.c_str(), str.length());
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputStream &OutputStream::operator<<(int64_t val) {
|
||||||
|
std::string str = std::to_string(val);
|
||||||
|
write(str.c_str(), str.length());
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputStream &OutputStream::operator<<(float val) {
|
||||||
|
std::string str = std::to_string(val);
|
||||||
|
write(str.c_str(), str.length());
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputStream &OutputStream::operator<<(double val) {
|
||||||
|
std::string str = std::to_string(val);
|
||||||
|
write(str.c_str(), str.length());
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OutputMemoryStream::write(std::string &str) {
|
||||||
|
write(str.c_str(), str.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
void *OutputMemoryStream::skip(size_t size) {
|
||||||
|
if (size + usage > capacity) {
|
||||||
|
reserve((size + usage) << 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void* ret = (uint8_t*)buffer + usage;
|
||||||
|
usage += size;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputMemoryStream& OutputMemoryStream::operator+=(size_t size) {
|
||||||
|
skip(size);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputMemoryStream& OutputMemoryStream::operator++() {
|
||||||
|
skip(1);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t OutputMemoryStream::operator[](size_t index) const {
|
||||||
|
return buffer[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t &OutputMemoryStream::operator[](size_t index) {
|
||||||
|
return buffer[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OutputMemoryStream::write(const void *data, size_t size) {
|
||||||
|
if (!size) return true;
|
||||||
|
|
||||||
|
if (usage + size > capacity) {
|
||||||
|
reserve((usage + size) << 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy((uint8_t*)data + usage, data, size);
|
||||||
|
usage += size;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OutputMemoryStream::clear() { usage = 0; }
|
||||||
|
|
||||||
|
void OutputMemoryStream::free() {
|
||||||
|
usage = 0;
|
||||||
|
capacity = 0;
|
||||||
|
delete[] buffer;
|
||||||
|
buffer = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OutputMemoryStream::reserve(size_t size) {
|
||||||
|
if (size < capacity) return;
|
||||||
|
|
||||||
|
auto* temp = static_cast<uint8_t *>(malloc(size));
|
||||||
|
memcpy(temp, buffer, capacity);
|
||||||
|
delete[] buffer;
|
||||||
|
buffer = temp;
|
||||||
|
capacity = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t *OutputMemoryStream::release() {
|
||||||
|
auto* temp = static_cast<uint8_t *>(malloc(usage));
|
||||||
|
memcpy(temp, buffer, usage);
|
||||||
|
free();
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
InputMemoryStream::InputMemoryStream(const void *data, size_t size)
|
||||||
|
: data(static_cast<const uint8_t *>(data)), capacity(size), position(0) {}
|
||||||
|
|
||||||
|
InputMemoryStream::InputMemoryStream(const ShadowEngine::OutputMemoryStream &blob)
|
||||||
|
: data(blob.data()), capacity(blob.size()), position(0) {}
|
||||||
|
|
||||||
|
void InputMemoryStream::set(const void *newData, size_t size) {
|
||||||
|
data = (uint8_t*) newData; capacity = size; position = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const void *InputMemoryStream::skip(size_t size) {
|
||||||
|
auto* pos = data + position;
|
||||||
|
position += size;
|
||||||
|
if (position > capacity) {
|
||||||
|
position = capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (const void*) pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InputMemoryStream::read(void *out, size_t size) {
|
||||||
|
if (position + (uint32_t) size > capacity) {
|
||||||
|
for (int32_t i = 0; i < size; i++)
|
||||||
|
((unsigned char*)out)[i] = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size) {
|
||||||
|
memcpy(out, ((char*)data) + position, capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
position += size;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string InputMemoryStream::readString() {
|
||||||
|
const char* ret = (const char*) data + position;
|
||||||
|
while (position < capacity && data[position]) ++position;
|
||||||
|
++position;
|
||||||
|
|
||||||
|
return { ret };
|
||||||
|
}
|
||||||
|
}
|
131
projs/shadow/shadow-engine/shadow-assets/src/fs/iostream.h
Normal file
131
projs/shadow/shadow-engine/shadow-assets/src/fs/iostream.h
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace ShadowEngine {
|
||||||
|
// A custom OutputStream that can be implemented to output to any arbitrary data structure.
|
||||||
|
// The idea is that it can write to a file, or into memory, or into a temporary buffer that is copied to both.
|
||||||
|
// As opposed to the hardcoded streams that exist in C++, which have a single purpose for their entire lifetime.
|
||||||
|
struct OutputStream {
|
||||||
|
virtual bool write(const void* data, size_t size) = 0;
|
||||||
|
|
||||||
|
OutputStream& operator<< (std::string& str);
|
||||||
|
OutputStream& operator<< (const char* str);
|
||||||
|
OutputStream& operator<< (size_t val);
|
||||||
|
OutputStream& operator<< (int64_t val);
|
||||||
|
OutputStream& operator<< (uint32_t val);
|
||||||
|
OutputStream& operator<< (int32_t val);
|
||||||
|
OutputStream& operator<< (float val);
|
||||||
|
OutputStream& operator<< (double val);
|
||||||
|
template <class T> bool write(const T& val);
|
||||||
|
};
|
||||||
|
|
||||||
|
// A custom InputStream that can be implemented to read from any arbitrary data structure.
|
||||||
|
// The idea is that it can read from a file, or from memory, or from a temporary buffer that is merged from both.
|
||||||
|
// As opposed to the hardcoded streams that exist in C++, which have a single purpose for their entire lifetime.
|
||||||
|
struct InputStream {
|
||||||
|
virtual bool read(void* buffer, size_t size) = 0;
|
||||||
|
virtual const void* getBuffer() const = 0;
|
||||||
|
virtual size_t size() const = 0;
|
||||||
|
|
||||||
|
template <class T> void read(T& val) { read(&val, sizeof(T)); }
|
||||||
|
template <class T> T read();
|
||||||
|
};
|
||||||
|
|
||||||
|
// A custom OutputStream that writes to memory.
|
||||||
|
struct OutputMemoryStream final : OutputStream {
|
||||||
|
|
||||||
|
OutputMemoryStream();
|
||||||
|
OutputMemoryStream(void* data, size_t size);
|
||||||
|
OutputMemoryStream(OutputMemoryStream&& str) noexcept;
|
||||||
|
OutputMemoryStream(const OutputMemoryStream& rhs) noexcept;
|
||||||
|
~OutputMemoryStream();
|
||||||
|
|
||||||
|
OutputMemoryStream& operator= (const OutputMemoryStream& rhs) noexcept;
|
||||||
|
OutputMemoryStream& operator= (OutputMemoryStream&& rhs) noexcept;
|
||||||
|
|
||||||
|
uint8_t operator[] (size_t index) const;
|
||||||
|
uint8_t& operator[] (size_t index);
|
||||||
|
OutputMemoryStream& operator+= (size_t index);
|
||||||
|
OutputMemoryStream& operator++ ();
|
||||||
|
|
||||||
|
bool write(const void* data, size_t size) override;
|
||||||
|
|
||||||
|
uint8_t* release();
|
||||||
|
void resize(size_t size);
|
||||||
|
void reserve(size_t size);
|
||||||
|
const uint8_t* data() const { return buffer; };
|
||||||
|
uint8_t* dataMut() { return buffer; };
|
||||||
|
size_t size() const { return usage; };
|
||||||
|
void clear();
|
||||||
|
void* skip(size_t size);
|
||||||
|
bool empty() const { return usage == 0; };
|
||||||
|
void free();
|
||||||
|
|
||||||
|
void write(std::string& str);
|
||||||
|
template <class T> void write(const T& val);
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8_t* buffer;
|
||||||
|
size_t capacity;
|
||||||
|
size_t usage;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T> void OutputMemoryStream::write(const T& val){
|
||||||
|
write(&val, sizeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template <> inline void OutputMemoryStream::write<bool>(const bool& val) {
|
||||||
|
uint8_t v = val;
|
||||||
|
write(&v, sizeof(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
// A custom InputStream that writes from memory.
|
||||||
|
struct InputMemoryStream final : InputStream {
|
||||||
|
InputMemoryStream(const void* data, size_t size);
|
||||||
|
explicit InputMemoryStream(const OutputMemoryStream& blob);
|
||||||
|
|
||||||
|
void set(const void* data, size_t size);
|
||||||
|
bool read(void* data, size_t size) override;
|
||||||
|
std::string readString();
|
||||||
|
const void* skip(size_t size);
|
||||||
|
const void* getData() const { return data; };
|
||||||
|
const void* getBuffer() const override { return data; };
|
||||||
|
size_t size() const override { return capacity; };
|
||||||
|
size_t pos() const { return position; };
|
||||||
|
void setPos(size_t pos) { position = pos; };
|
||||||
|
void restart() { position = 0; };
|
||||||
|
uint8_t readChar() { position++; return data[position-1]; };
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
T getAs() const {
|
||||||
|
static_assert(position + sizeof(T) < capacity);
|
||||||
|
return *(T*)(data + position);
|
||||||
|
}
|
||||||
|
|
||||||
|
using InputStream::read;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const uint8_t* data;
|
||||||
|
size_t capacity;
|
||||||
|
size_t position;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
T InputStream::read() {
|
||||||
|
T v;
|
||||||
|
read(&v, sizeof(T));
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<> inline bool InputStream::read<bool>() {
|
||||||
|
uint8_t v;
|
||||||
|
read(&v, sizeof(bool));
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
bool OutputStream::write(const T &val) {
|
||||||
|
return write(&val, sizeof(T));
|
||||||
|
}
|
||||||
|
}
|
118
projs/shadow/shadow-engine/shadow-assets/src/fs/path.cpp
Normal file
118
projs/shadow/shadow-engine/shadow-assets/src/fs/path.cpp
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
#include <fs/path.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <str/string.h>
|
||||||
|
|
||||||
|
namespace ShadowEngine {
|
||||||
|
|
||||||
|
Path::Path() : path {} { }
|
||||||
|
|
||||||
|
Path::Path(const std::string &str) {
|
||||||
|
set(normalise((std::string&) str));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Path::set(const std::string &str) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
std::string temp = Str::toLower((std::string&) str);
|
||||||
|
hash = PathHash(temp);
|
||||||
|
#else
|
||||||
|
hash = PathHash(str);
|
||||||
|
#endif
|
||||||
|
path = str;
|
||||||
|
}
|
||||||
|
|
||||||
|
Path& Path::operator=(const std::string &rhs) {
|
||||||
|
set(rhs);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Path::operator==(const std::string &rhs) {
|
||||||
|
return path == rhs;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Path::operator==(const ShadowEngine::Path &rhs) {
|
||||||
|
return path == rhs.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Path::operator!=(const ShadowEngine::Path &rhs) {
|
||||||
|
return path != rhs.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Path::normalise(std::string &str) {
|
||||||
|
bool prevSlash = false;
|
||||||
|
|
||||||
|
std::string temp;
|
||||||
|
const char* path = str.c_str();
|
||||||
|
size_t len = str.length();
|
||||||
|
size_t i = 0;
|
||||||
|
|
||||||
|
// Skip initial stuff.
|
||||||
|
size_t ind = str.find_first_of(":");
|
||||||
|
path += ind;
|
||||||
|
if (path[0] == '.' && (path[1] == '\\' || path[1] == '/'))
|
||||||
|
path += 2;
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (path[0] == '\\' || path[0] == '/')
|
||||||
|
++path;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
while (*path != '\0' && i < len) {
|
||||||
|
bool slash = *path == '\\' || *path == '/';
|
||||||
|
|
||||||
|
// Skip double slashes.
|
||||||
|
if (slash && prevSlash) {
|
||||||
|
path++; continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert backslashes to forward slashes.
|
||||||
|
temp.append(std::to_string(*path == '\\' ? '/' : *path));
|
||||||
|
|
||||||
|
path++; i++; prevSlash = slash;
|
||||||
|
}
|
||||||
|
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Path::getPrelude(std::string &path) {
|
||||||
|
return path.substr(0, path.find_first_of(":"));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Path::getDomain(std::string &path) {
|
||||||
|
return path.substr(path.find_first_of(":"), path.find_first_of("/"));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Path::getDirectory(std::string &path) {
|
||||||
|
return path.substr(path.find_first_of(":"), path.find_last_of("/"));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Path::getFilename(std::string &path) {
|
||||||
|
return path.substr(path.find_last_of("/"), path.find_last_of("."));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Path::getExtension(std::string &path) {
|
||||||
|
return path.substr(path.find_last_of("."), path.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Path::replaceExtension(std::string &path, std::string &newExt) {
|
||||||
|
return path.substr(0, path.length() - newExt.length()).append(newExt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Path::hasExtension(std::string &path, std::string &ext) {
|
||||||
|
return path.find_last_of(ext) == path.length() - ext.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
PathInfo::PathInfo(std::string &str) {
|
||||||
|
std::string normalised = Path::normalise(str);
|
||||||
|
|
||||||
|
std::string preludeS = Path::getPrelude(normalised);
|
||||||
|
memcpy_s(prelude, 10, preludeS.c_str(), preludeS.length());
|
||||||
|
std::string domainS = Path::getDomain(normalised);
|
||||||
|
memcpy_s(domain, 256, domainS.c_str(), domainS.length());
|
||||||
|
std::string directoryS = Path::getDirectory(normalised);
|
||||||
|
memcpy_s(directory, 256, directoryS.c_str(), directoryS.length());
|
||||||
|
std::string filenameS = Path::getFilename(normalised);
|
||||||
|
memcpy_s(baseName, 256, filenameS.c_str(), filenameS.length());
|
||||||
|
std::string extensionS = Path::getExtension(normalised);
|
||||||
|
memcpy_s(extension, 10, extensionS.c_str(), extensionS.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
67
projs/shadow/shadow-engine/shadow-assets/src/fs/path.h
Normal file
67
projs/shadow/shadow-engine/shadow-assets/src/fs/path.h
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <fs/hash.h>
|
||||||
|
|
||||||
|
namespace ShadowEngine {
|
||||||
|
/**
|
||||||
|
* Stores split data about a path, for easy referencing and decomposition.
|
||||||
|
* Not to be used as a replacement for the Path class.
|
||||||
|
*/
|
||||||
|
struct PathInfo {
|
||||||
|
explicit PathInfo(std::string& str);
|
||||||
|
|
||||||
|
char extension[10];
|
||||||
|
char baseName[256];
|
||||||
|
char directory[256];
|
||||||
|
char domain[256];
|
||||||
|
char prelude[10];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores and handles paths in the VFS.
|
||||||
|
* All operations are copy-instantiated, nothing works in-place.
|
||||||
|
* A typical path is of the form:
|
||||||
|
* prelude:/domain/directory/filename.extension
|
||||||
|
*/
|
||||||
|
struct Path {
|
||||||
|
// Make sure the path is valid.
|
||||||
|
// Always from the root.
|
||||||
|
// One slash separating.
|
||||||
|
static std::string normalise(std::string& path);
|
||||||
|
// Get the prelude of the given path.
|
||||||
|
static std::string getPrelude(std::string& path);
|
||||||
|
// Get the domain of the given path.
|
||||||
|
static std::string getDomain(std::string& path);
|
||||||
|
// Get the directory of the given path.
|
||||||
|
static std::string getDirectory(std::string& path);
|
||||||
|
// Get the name of the file of the given path.
|
||||||
|
static std::string getFilename(std::string& path);
|
||||||
|
// Get the file extension of the given path.
|
||||||
|
static std::string getExtension(std::string& path);
|
||||||
|
// Check if the path has the given extension.
|
||||||
|
static bool hasExtension(std::string& path, std::string& ext);
|
||||||
|
// Replace the extension of the given path.
|
||||||
|
static std::string replaceExtension(std::string& path, std::string& newExt);
|
||||||
|
|
||||||
|
Path();
|
||||||
|
explicit Path(const std::string& str);
|
||||||
|
|
||||||
|
Path& operator=(const std::string& rhs);
|
||||||
|
bool operator==(const std::string& rhs);
|
||||||
|
bool operator==(const Path& rhs);
|
||||||
|
bool operator!=(const Path& rhs);
|
||||||
|
|
||||||
|
// Use this to set a new value into the path; it handles the hash too.
|
||||||
|
void set(const std::string& path);
|
||||||
|
|
||||||
|
[[nodiscard]] uint32_t length() const { return path.length(); };
|
||||||
|
[[nodiscard]] PathHash getHash() const { return hash; }
|
||||||
|
[[nodiscard]] const char* c_str() const { return path.data(); }
|
||||||
|
[[nodiscard]] std::string const& get() const { return path; }
|
||||||
|
[[nodiscard]] bool isEmpty() const { return path.length() == 0; }
|
||||||
|
private:
|
||||||
|
std::string path;
|
||||||
|
PathHash hash;
|
||||||
|
};
|
||||||
|
}
|
182
projs/shadow/shadow-engine/shadow-assets/src/fs/xxhash.h
Normal file
182
projs/shadow/shadow-engine/shadow-assets/src/fs/xxhash.h
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
#pragma once
|
||||||
|
#include <stdint.h> // for uint32_t and uint64_t
|
||||||
|
|
||||||
|
class XXHash64
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// create new XXHash (64 bit)
|
||||||
|
/** @param seed your seed value, even zero is a valid seed **/
|
||||||
|
explicit XXHash64(uint64_t seed)
|
||||||
|
{
|
||||||
|
state[0] = seed + Prime1 + Prime2;
|
||||||
|
state[1] = seed + Prime2;
|
||||||
|
state[2] = seed;
|
||||||
|
state[3] = seed - Prime1;
|
||||||
|
bufferSize = 0;
|
||||||
|
totalLength = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// add a chunk of bytes
|
||||||
|
/** @param input pointer to a continuous block of data
|
||||||
|
@param length number of bytes
|
||||||
|
@return false if parameters are invalid / zero **/
|
||||||
|
bool add(const void* input, uint64_t length)
|
||||||
|
{
|
||||||
|
// no data ?
|
||||||
|
if (!input || length == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
totalLength += length;
|
||||||
|
// byte-wise access
|
||||||
|
const unsigned char* data = (const unsigned char*)input;
|
||||||
|
|
||||||
|
// unprocessed old data plus new data still fit in temporary buffer ?
|
||||||
|
if (bufferSize + length < MaxBufferSize)
|
||||||
|
{
|
||||||
|
// just add new data
|
||||||
|
while (length-- > 0)
|
||||||
|
buffer[bufferSize++] = *data++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// point beyond last byte
|
||||||
|
const unsigned char* stop = data + length;
|
||||||
|
const unsigned char* stopBlock = stop - MaxBufferSize;
|
||||||
|
|
||||||
|
// some data left from previous update ?
|
||||||
|
if (bufferSize > 0)
|
||||||
|
{
|
||||||
|
// make sure temporary buffer is full (16 bytes)
|
||||||
|
while (bufferSize < MaxBufferSize)
|
||||||
|
buffer[bufferSize++] = *data++;
|
||||||
|
|
||||||
|
// process these 32 bytes (4x8)
|
||||||
|
process(buffer, state[0], state[1], state[2], state[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// copying state to local variables helps optimizer A LOT
|
||||||
|
uint64_t s0 = state[0], s1 = state[1], s2 = state[2], s3 = state[3];
|
||||||
|
// 32 bytes at once
|
||||||
|
while (data <= stopBlock)
|
||||||
|
{
|
||||||
|
// local variables s0..s3 instead of state[0]..state[3] are much faster
|
||||||
|
process(data, s0, s1, s2, s3);
|
||||||
|
data += 32;
|
||||||
|
}
|
||||||
|
// copy back
|
||||||
|
state[0] = s0; state[1] = s1; state[2] = s2; state[3] = s3;
|
||||||
|
|
||||||
|
// copy remainder to temporary buffer
|
||||||
|
bufferSize = stop - data;
|
||||||
|
for (uint64_t i = 0; i < bufferSize; i++)
|
||||||
|
buffer[i] = data[i];
|
||||||
|
|
||||||
|
// done
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// get current hash
|
||||||
|
/** @return 64 bit XXHash **/
|
||||||
|
uint64_t hash() const
|
||||||
|
{
|
||||||
|
// fold 256 bit state into one single 64 bit value
|
||||||
|
uint64_t result;
|
||||||
|
if (totalLength >= MaxBufferSize)
|
||||||
|
{
|
||||||
|
result = rotateLeft(state[0], 1) +
|
||||||
|
rotateLeft(state[1], 7) +
|
||||||
|
rotateLeft(state[2], 12) +
|
||||||
|
rotateLeft(state[3], 18);
|
||||||
|
result = (result ^ processSingle(0, state[0])) * Prime1 + Prime4;
|
||||||
|
result = (result ^ processSingle(0, state[1])) * Prime1 + Prime4;
|
||||||
|
result = (result ^ processSingle(0, state[2])) * Prime1 + Prime4;
|
||||||
|
result = (result ^ processSingle(0, state[3])) * Prime1 + Prime4;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// internal state wasn't set in add(), therefore original seed is still stored in state2
|
||||||
|
result = state[2] + Prime5;
|
||||||
|
}
|
||||||
|
|
||||||
|
result += totalLength;
|
||||||
|
|
||||||
|
// process remaining bytes in temporary buffer
|
||||||
|
const unsigned char* data = buffer;
|
||||||
|
// point beyond last byte
|
||||||
|
const unsigned char* stop = data + bufferSize;
|
||||||
|
|
||||||
|
// at least 8 bytes left ? => eat 8 bytes per step
|
||||||
|
for (; data + 8 <= stop; data += 8)
|
||||||
|
result = rotateLeft(result ^ processSingle(0, *(uint64_t*)data), 27) * Prime1 + Prime4;
|
||||||
|
|
||||||
|
// 4 bytes left ? => eat those
|
||||||
|
if (data + 4 <= stop)
|
||||||
|
{
|
||||||
|
result = rotateLeft(result ^ (*(uint32_t*)data) * Prime1, 23) * Prime2 + Prime3;
|
||||||
|
data += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// take care of remaining 0..3 bytes, eat 1 byte per step
|
||||||
|
while (data != stop)
|
||||||
|
result = rotateLeft(result ^ (*data++) * Prime5, 11) * Prime1;
|
||||||
|
|
||||||
|
// mix bits
|
||||||
|
result ^= result >> 33;
|
||||||
|
result *= Prime2;
|
||||||
|
result ^= result >> 29;
|
||||||
|
result *= Prime3;
|
||||||
|
result ^= result >> 32;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// combine constructor, add() and hash() in one static function (C style)
|
||||||
|
/** @param input pointer to a continuous block of data
|
||||||
|
@param length number of bytes
|
||||||
|
@param seed your seed value, e.g. zero is a valid seed
|
||||||
|
@return 64 bit XXHash **/
|
||||||
|
static uint64_t hash(const void* input, uint64_t length, uint64_t seed)
|
||||||
|
{
|
||||||
|
XXHash64 hasher(seed);
|
||||||
|
hasher.add(input, length);
|
||||||
|
return hasher.hash();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// magic constants :-)
|
||||||
|
static const uint64_t Prime1 = 11400714785074694791ULL;
|
||||||
|
static const uint64_t Prime2 = 14029467366897019727ULL;
|
||||||
|
static const uint64_t Prime3 = 1609587929392839161ULL;
|
||||||
|
static const uint64_t Prime4 = 9650029242287828579ULL;
|
||||||
|
static const uint64_t Prime5 = 2870177450012600261ULL;
|
||||||
|
|
||||||
|
/// temporarily store up to 31 bytes between multiple add() calls
|
||||||
|
static const uint64_t MaxBufferSize = 31+1;
|
||||||
|
|
||||||
|
uint64_t state[4];
|
||||||
|
unsigned char buffer[MaxBufferSize];
|
||||||
|
uint64_t bufferSize;
|
||||||
|
uint64_t totalLength;
|
||||||
|
|
||||||
|
/// rotate bits, should compile to a single CPU instruction (ROL)
|
||||||
|
static inline uint64_t rotateLeft(uint64_t x, unsigned char bits)
|
||||||
|
{
|
||||||
|
return (x << bits) | (x >> (64 - bits));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// process a single 64 bit value
|
||||||
|
static inline uint64_t processSingle(uint64_t previous, uint64_t input)
|
||||||
|
{
|
||||||
|
return rotateLeft(previous + input * Prime2, 31) * Prime1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// process a block of 4x4 bytes, this is the main part of the XXHash32 algorithm
|
||||||
|
static inline void process(const void* data, uint64_t& state0, uint64_t& state1, uint64_t& state2, uint64_t& state3)
|
||||||
|
{
|
||||||
|
const uint64_t* block = (const uint64_t*) data;
|
||||||
|
state0 = processSingle(state0, block[0]);
|
||||||
|
state1 = processSingle(state1, block[1]);
|
||||||
|
state2 = processSingle(state2, block[2]);
|
||||||
|
state3 = processSingle(state3, block[3]);
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,92 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace ShadowEngine {
|
||||||
|
|
||||||
|
template <typename T> struct Delegate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple, generic callback template.
|
||||||
|
* A Delegate takes the form of a std::function that can be called, redirected and bound freely.
|
||||||
|
* Advantages over std::function includes the ability to reference types that aren't yet fully qualified.
|
||||||
|
*
|
||||||
|
* Idea taken from https://blog.molecular-matters.com/2011/09/19/generic-type-safe-delegates-and-events-in-c/
|
||||||
|
*
|
||||||
|
* @tparam R the return type
|
||||||
|
* @tparam Args the arguments to the function
|
||||||
|
*/
|
||||||
|
template <typename R, typename... Args> struct Delegate<R(Args...)> {
|
||||||
|
private:
|
||||||
|
using InstancePtr = void*;
|
||||||
|
using InternalFunction = R (*)(InstancePtr, Args...);
|
||||||
|
struct Stub {
|
||||||
|
InstancePtr first;
|
||||||
|
InternalFunction second;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <R (*Function)(Args...)> static R FunctionStub(InstancePtr, Args... args) {
|
||||||
|
return (Function)(args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename C, R(C::*Function)(Args...)>
|
||||||
|
static R ClassMethodStub(InstancePtr instance, Args... args) {
|
||||||
|
return (static_cast<C*>(instance)->*Function)(args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename C, R(C::*Function)(Args...) const>
|
||||||
|
static R ClassMethodStub(InstancePtr instance, Args... args) {
|
||||||
|
return (static_cast<C*>(instance)->*Function)(args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
Delegate() {
|
||||||
|
m_stub.first = nullptr;
|
||||||
|
m_stub.second = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
Delegate(const T& obj) {
|
||||||
|
m_stub.first = (InstancePtr)&obj;
|
||||||
|
m_stub.second = [](InstancePtr inst, Args... args) -> R {
|
||||||
|
const T& obj = *(const T*)inst;
|
||||||
|
return obj(args...);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isValid() { return m_stub.second != nullptr; }
|
||||||
|
|
||||||
|
template <R (*Function)(Args...)> void bind() {
|
||||||
|
m_stub.first = nullptr;
|
||||||
|
m_stub.second = &FunctionStub<Function>;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <auto F, typename C> void bind(C* instance) {
|
||||||
|
m_stub.first = instance;
|
||||||
|
m_stub.second = &ClassMethodStub<C, F>;
|
||||||
|
}
|
||||||
|
|
||||||
|
R invoke(Args... args) const {
|
||||||
|
return m_stub.second(m_stub.first, args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const Delegate<R(Args...)>& rhs) {
|
||||||
|
return m_stub.first == rhs.m_stub.first && m_stub.second == rhs.m_stub.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Stub m_stub;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T> struct ToDelegate_T;
|
||||||
|
template <typename R, typename C, typename... Args> struct ToDelegate_T<R (C::*)(Args...)> {
|
||||||
|
using Type = Delegate<R (Args...)>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T> using ToDelegate = typename ToDelegate_T<T>::Type;
|
||||||
|
|
||||||
|
template <auto M, typename C>
|
||||||
|
auto makeDelegate(C* inst) {
|
||||||
|
ToDelegate<decltype(M)> res;
|
||||||
|
res.template bind<M, C>(inst);
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
#pragma once
|
||||||
|
#include <management/delegate.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace ShadowEngine {
|
||||||
|
|
||||||
|
template <typename T> struct DelegateList;
|
||||||
|
|
||||||
|
template <typename R, typename... Args> struct DelegateList<R(Args...)> {
|
||||||
|
DelegateList() = default;
|
||||||
|
|
||||||
|
template <auto Function, typename C> void bind(C* instance) {
|
||||||
|
Delegate<R(Args...)> cb;
|
||||||
|
cb.template bind<Function>(instance);
|
||||||
|
m_delegates.push_back(cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <R (*Function)(Args...)> void bind() {
|
||||||
|
Delegate<R(Args...)> cb;
|
||||||
|
cb.template bind<Function>();
|
||||||
|
m_delegates.push_back(cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <R (*Function)(Args...)> void unbind() {
|
||||||
|
Delegate<R(Args...)> cb;
|
||||||
|
cb.template bind<Function>();
|
||||||
|
for (int i = 0; i < m_delegates.size(); ++i)
|
||||||
|
{
|
||||||
|
if (m_delegates[i] == cb)
|
||||||
|
{
|
||||||
|
m_delegates.swapAndPop(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <auto Function, typename C> void unbind(C* instance) {
|
||||||
|
Delegate<R(Args...)> cb;
|
||||||
|
cb.template bind<Function>(instance);
|
||||||
|
for (int i = 0; i < m_delegates.size(); ++i)
|
||||||
|
{
|
||||||
|
if (m_delegates[i] == cb)
|
||||||
|
{
|
||||||
|
m_delegates.swapAndPop(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void invoke(Args... args) {
|
||||||
|
for (uint32_t i = 0, c = m_delegates.size(); i < c; ++i) m_delegates[i].invoke(args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<Delegate<R(Args...)>> m_delegates;
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
#include <management/synchronization.h>
|
||||||
|
|
||||||
|
// This doesn't work on Linux. Sucks to be you? dpeter won't let me do system-specific source files.
|
||||||
|
#ifdef _WIN32
|
||||||
|
|
||||||
|
#include <intrin.h>
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
|
||||||
|
namespace ShadowEngine { struct NewPlaceholder {}; }
|
||||||
|
inline void* operator new(size_t, ShadowEngine::NewPlaceholder, void* where) { return where; }
|
||||||
|
inline void operator delete(void*, ShadowEngine::NewPlaceholder, void*) { }
|
||||||
|
|
||||||
|
namespace ShadowEngine {
|
||||||
|
|
||||||
|
Semaphore::Semaphore(int initCount, int maxCount) {
|
||||||
|
id = ::CreateSemaphore(nullptr, initCount, maxCount, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
Semaphore::~Semaphore() {
|
||||||
|
::CloseHandle(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Semaphore::raise() {
|
||||||
|
::ReleaseSemaphore(id, 1, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Semaphore::wait() {
|
||||||
|
::WaitForSingleObject(id, INFINITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConditionVariable::ConditionVariable() {
|
||||||
|
memset(data, 0, sizeof(data));
|
||||||
|
auto* var = new (NewPlaceholder(), data) CONDITION_VARIABLE;
|
||||||
|
InitializeConditionVariable(var);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConditionVariable::~ConditionVariable() {
|
||||||
|
((CONDITION_VARIABLE*)data)->~CONDITION_VARIABLE();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConditionVariable::sleep(ShadowEngine::Mutex &mut) {
|
||||||
|
::SleepConditionVariableSRW((CONDITION_VARIABLE*) data, (SRWLOCK*) mut.data, INFINITE, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConditionVariable::wake() {
|
||||||
|
::WakeConditionVariable((CONDITION_VARIABLE*) data);
|
||||||
|
}
|
||||||
|
|
||||||
|
Mutex::Mutex() {
|
||||||
|
memset(data, 0, sizeof(data));
|
||||||
|
auto* lock = new (NewPlaceholder(), data) SRWLOCK;
|
||||||
|
::InitializeSRWLock(lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
Mutex::~Mutex() {
|
||||||
|
auto* lock = (SRWLOCK*) data;
|
||||||
|
lock->~SRWLOCK();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mutex::enter() {
|
||||||
|
auto* lock = (SRWLOCK*) data;
|
||||||
|
::AcquireSRWLockExclusive(lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mutex::exit() {
|
||||||
|
auto* lock = (SRWLOCK*) data;
|
||||||
|
::ReleaseSRWLockExclusive(lock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,77 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#ifdef __linux__
|
||||||
|
#include <pthread.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace ShadowEngine {
|
||||||
|
// A simple synchronization system that allows one "accessing thread" at a time.
|
||||||
|
struct alignas(8) Mutex {
|
||||||
|
friend struct ConditionVariable;
|
||||||
|
Mutex();
|
||||||
|
Mutex(const Mutex&) = delete;
|
||||||
|
~Mutex();
|
||||||
|
|
||||||
|
void enter();
|
||||||
|
void exit();
|
||||||
|
private:
|
||||||
|
#ifdef _WIN32
|
||||||
|
uint8_t data[8] {};
|
||||||
|
#else
|
||||||
|
pthread_mutex_t mutex;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
// A simple synchronization system that allows many threads to wait for one thread to complete an operation.
|
||||||
|
struct Semaphore {
|
||||||
|
Semaphore(int initcount, int maxcount);
|
||||||
|
Semaphore(const Semaphore&) = delete;
|
||||||
|
~Semaphore();
|
||||||
|
|
||||||
|
void raise();
|
||||||
|
void wait();
|
||||||
|
private:
|
||||||
|
#ifdef _WIN32
|
||||||
|
void* id;
|
||||||
|
#else
|
||||||
|
struct {
|
||||||
|
pthread_mutex_t mutex;
|
||||||
|
pthread_mutex_cond cond;
|
||||||
|
volatile int32_t count;
|
||||||
|
} id;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ConditionVariable {
|
||||||
|
ConditionVariable();
|
||||||
|
ConditionVariable(const ConditionVariable&) = delete;
|
||||||
|
~ConditionVariable();
|
||||||
|
|
||||||
|
void sleep(Mutex& mut);
|
||||||
|
void wake();
|
||||||
|
|
||||||
|
private:
|
||||||
|
#ifdef _WIN32
|
||||||
|
uint8_t data[64];
|
||||||
|
#else
|
||||||
|
pthread_cond_t cond;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
// A simple RAII wrapper for Mutexes, that locks and unlocks as the Guard goes in and out of scope.
|
||||||
|
struct MutexGuard {
|
||||||
|
explicit MutexGuard(Mutex& mut) : mut(mut) {
|
||||||
|
mut.enter();
|
||||||
|
}
|
||||||
|
|
||||||
|
~MutexGuard() { mut.exit(); }
|
||||||
|
|
||||||
|
MutexGuard(const MutexGuard&) = delete;
|
||||||
|
void operator=(const MutexGuard&) = delete;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Mutex& mut;
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,194 @@
|
||||||
|
#include <resource/Resource.h>
|
||||||
|
#include <resource/ResourceManager.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
namespace ShadowEngine {
|
||||||
|
|
||||||
|
ResourceType::ResourceType(std::string& name) {
|
||||||
|
hash = HeapHash(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
Resource::Resource(const ShadowEngine::Path& path, ShadowEngine::ResourceTypeManager &manager)
|
||||||
|
: references(0),
|
||||||
|
emptyDependencies(0),
|
||||||
|
failedDependencies(0),
|
||||||
|
state(State::EMPTY),
|
||||||
|
desiredState(State::EMPTY),
|
||||||
|
path(path),
|
||||||
|
size(),
|
||||||
|
callback(),
|
||||||
|
manager(manager),
|
||||||
|
handle(FileSystem::AsyncHandle::invalid()) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Resource::~Resource() = default;
|
||||||
|
|
||||||
|
void Resource::refresh() {
|
||||||
|
if (state == State::EMPTY) return;
|
||||||
|
|
||||||
|
const State old = state;
|
||||||
|
state = State::EMPTY;
|
||||||
|
callback.invoke(old, state, *this);
|
||||||
|
checkState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Resource::checkState() {
|
||||||
|
State old = state;
|
||||||
|
if (failedDependencies > 0 && state != State::FAILED) {
|
||||||
|
state = State::FAILED;
|
||||||
|
} else if (failedDependencies == 0) {
|
||||||
|
if (emptyDependencies > 0 && state != State::EMPTY)
|
||||||
|
state = State::EMPTY;
|
||||||
|
|
||||||
|
if (emptyDependencies == 0 && state != State::READY && desiredState != State::EMPTY) {
|
||||||
|
onReadying();
|
||||||
|
|
||||||
|
if (emptyDependencies != 0 || state == State::READY || desiredState == State::EMPTY)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (failedDependencies != 0) {
|
||||||
|
checkState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state = State::READY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
callback.invoke(old, state, *this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Resource::fileLoaded(size_t fileSize, const uint8_t *mem, bool success) {
|
||||||
|
handle = FileSystem::AsyncHandle::invalid();
|
||||||
|
if (desiredState != State::READY) return;
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
ResourceManager& owner = getManager().getOwner();
|
||||||
|
if (!hooked && owner.isHooked()) {
|
||||||
|
if (owner.onLoad(*this) == ResourceManager::LoadHook::Action::DEFERRED) {
|
||||||
|
hooked = true;
|
||||||
|
desiredState = State::READY;
|
||||||
|
increaseReferences();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
--emptyDependencies;
|
||||||
|
++failedDependencies;
|
||||||
|
checkState();
|
||||||
|
handle = FileSystem::AsyncHandle::invalid();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ResourceHeader* header = (const ResourceHeader*) mem;
|
||||||
|
|
||||||
|
if (size < sizeof(*header)) {
|
||||||
|
spdlog::error("Invalid resource: ", path.get(), ": size mismatch. Expected ", fileSize, ", got " , sizeof(*header));
|
||||||
|
failedDependencies++;
|
||||||
|
} else if (header->magic != ResourceHeader::MAGIC) {
|
||||||
|
spdlog::error("Invalid resource: " , path.get(), ": magic number mismatch. Expected " , ResourceHeader::MAGIC, ", got ", header->magic);
|
||||||
|
failedDependencies++;
|
||||||
|
} else if (header->version > 0) {
|
||||||
|
spdlog::error("Invalid resource: ", path.get(), ": verison mismatch. Expected 0, got ", header->version);
|
||||||
|
failedDependencies++;
|
||||||
|
} else {
|
||||||
|
// TODO: Compression?
|
||||||
|
if (!load(size - sizeof(*header), mem + sizeof(*header)))
|
||||||
|
failedDependencies++;
|
||||||
|
size = header->decompressedSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
emptyDependencies--;
|
||||||
|
checkState();
|
||||||
|
handle = FileSystem::AsyncHandle::invalid();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Resource::performUnload() {
|
||||||
|
if (handle.valid()) {
|
||||||
|
FileSystem& fs = manager.getOwner().getFileSystem();
|
||||||
|
fs.cancelAsync(handle);
|
||||||
|
handle = FileSystem::AsyncHandle::invalid();
|
||||||
|
}
|
||||||
|
|
||||||
|
hooked = false;
|
||||||
|
desiredState = State::EMPTY;
|
||||||
|
unload();
|
||||||
|
|
||||||
|
size = 0;
|
||||||
|
emptyDependencies = 1;
|
||||||
|
failedDependencies = 0;
|
||||||
|
checkState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Resource::onCreated(ShadowEngine::Resource::State newState) {
|
||||||
|
state = newState;
|
||||||
|
desiredState = State::READY;
|
||||||
|
failedDependencies = state == State::FAILED ? 1 : 0;
|
||||||
|
emptyDependencies = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Resource::doLoad() {
|
||||||
|
if (desiredState == State::READY) return;
|
||||||
|
desiredState = State::READY;
|
||||||
|
|
||||||
|
if (handle.valid()) return;
|
||||||
|
|
||||||
|
FileSystem& fs = manager.getOwner().getFileSystem();
|
||||||
|
FileSystem::ContentCallback cb = makeDelegate<&Resource::fileLoaded>(this);
|
||||||
|
|
||||||
|
const PathHash hash = path.getHash();
|
||||||
|
Path resourcePath("./resources/" + std::to_string(hash.getHash()) + ".res");
|
||||||
|
handle = fs.readAsync(resourcePath, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Resource::addDependency(ShadowEngine::Resource &dependent) {
|
||||||
|
dependent.callback.bind<&Resource::stateChanged>(this);
|
||||||
|
if (dependent.isEmpty()) emptyDependencies++;
|
||||||
|
if (dependent.isFailure()) failedDependencies++;
|
||||||
|
|
||||||
|
checkState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Resource::removeDependency(ShadowEngine::Resource &dependent) {
|
||||||
|
dependent.callback.unbind<&Resource::stateChanged>(this);
|
||||||
|
if (dependent.isEmpty()) --emptyDependencies;
|
||||||
|
if (dependent.isFailure()) --failedDependencies;
|
||||||
|
|
||||||
|
checkState();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t Resource::decreaseReferences() {
|
||||||
|
--references;
|
||||||
|
if (references == 0 && manager.unloadEnabled)
|
||||||
|
performUnload();
|
||||||
|
|
||||||
|
return references;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Resource::stateChanged(ShadowEngine::Resource::State old, ShadowEngine::Resource::State newState,
|
||||||
|
ShadowEngine::Resource &) {
|
||||||
|
if (old == State::EMPTY) --emptyDependencies;
|
||||||
|
if (old == State::FAILED) --failedDependencies;
|
||||||
|
|
||||||
|
if (newState == State::EMPTY) ++emptyDependencies;
|
||||||
|
if (newState == State::FAILED) ++failedDependencies;
|
||||||
|
|
||||||
|
checkState();
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string type("prefab");
|
||||||
|
const ResourceType PrefabResource::TYPE(type);
|
||||||
|
|
||||||
|
PrefabResource::PrefabResource(const ShadowEngine::Path &path,
|
||||||
|
ShadowEngine::ResourceTypeManager &resource_manager) : Resource(path, resource_manager) {}
|
||||||
|
|
||||||
|
ResourceType PrefabResource::getType() const { return TYPE; }
|
||||||
|
|
||||||
|
void PrefabResource::unload() { data.clear(); }
|
||||||
|
|
||||||
|
bool PrefabResource::load(size_t size, const uint8_t *mem) {
|
||||||
|
data.resize(size);
|
||||||
|
memcpy(data.dataMut(), mem, size);
|
||||||
|
hash = StableHash(mem, size);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
140
projs/shadow/shadow-engine/shadow-assets/src/resource/Resource.h
Normal file
140
projs/shadow/shadow-engine/shadow-assets/src/resource/Resource.h
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "fs/hash.h"
|
||||||
|
#include "fs/path.h"
|
||||||
|
#include "fs/file.h"
|
||||||
|
#include <management/delegate_list.h>
|
||||||
|
|
||||||
|
namespace ShadowEngine {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A runtime-only struct that determines the type of a resource - whether it be a texture, mesh, animation, or other data.
|
||||||
|
* Provides some specializations for living in a map.
|
||||||
|
*/
|
||||||
|
struct ResourceType {
|
||||||
|
ResourceType() = default;
|
||||||
|
explicit ResourceType(std::string& name);
|
||||||
|
bool operator!=(const ResourceType& o) const { return o.hash != hash; }
|
||||||
|
bool operator==(const ResourceType& o) const { return o.hash == hash; }
|
||||||
|
bool operator< (const ResourceType& o) const { return o.hash.getHash() < hash.getHash(); }
|
||||||
|
bool isValid() const { return hash.getHash() != 0; }
|
||||||
|
|
||||||
|
HeapHash hash;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A Resource Type that is guaranteed to be invalid.
|
||||||
|
static std::string empty;
|
||||||
|
const ResourceType INVALID_RESOURCE(empty);
|
||||||
|
|
||||||
|
// A specialization of HashFunc for ResourceTypes, since they already have a HeapHash within.
|
||||||
|
template<> struct HashFunc<ResourceType> {
|
||||||
|
static uint32_t get(const ResourceType& key) { return HashFunc<HeapHash>::get(key.hash); }
|
||||||
|
};
|
||||||
|
|
||||||
|
#pragma pack(1)
|
||||||
|
struct ResourceHeader {
|
||||||
|
static const uint32_t MAGIC = 'VXIP';
|
||||||
|
uint32_t magic = MAGIC; // VXI Package header
|
||||||
|
uint32_t version = 0;
|
||||||
|
uint32_t flags = 0;
|
||||||
|
uint32_t padding = 0;
|
||||||
|
uint32_t decompressedSize = 0;
|
||||||
|
};
|
||||||
|
#pragma pack()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A basic Resource type.
|
||||||
|
* Represents a single file loaded from disk.
|
||||||
|
* May have dependencies on other Resources, and other Resources may depend on this.
|
||||||
|
* Resources are reference-counted, and are removed when they go out of usage.
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct Resource {
|
||||||
|
|
||||||
|
friend struct ResourceTypeManager;
|
||||||
|
friend struct ResourceManager;
|
||||||
|
|
||||||
|
enum class State : uint32_t {
|
||||||
|
EMPTY = 0,
|
||||||
|
READY,
|
||||||
|
FAILED
|
||||||
|
};
|
||||||
|
|
||||||
|
using Observer = DelegateList<void(State, State, Resource&)>;
|
||||||
|
|
||||||
|
virtual ~Resource();
|
||||||
|
virtual ResourceType getType() const = 0;
|
||||||
|
State getState() const { return state; }
|
||||||
|
|
||||||
|
bool isEmpty() const { return state == State::EMPTY; }
|
||||||
|
bool isReady() const { return state == State::READY; }
|
||||||
|
bool isFailure() const { return state == State::FAILED; }
|
||||||
|
|
||||||
|
uint32_t getReferenceCount() const { return references; }
|
||||||
|
|
||||||
|
Observer const& getCallback() const { return callback; }
|
||||||
|
size_t getSize() const { return size; }
|
||||||
|
|
||||||
|
const Path& getPath() const { return path; }
|
||||||
|
|
||||||
|
struct ResourceTypeManager& getManager() { return manager; }
|
||||||
|
|
||||||
|
uint32_t decreaseReferences();
|
||||||
|
uint32_t increaseReferences() { return references++; }
|
||||||
|
|
||||||
|
bool toInitialize() const { return desiredState == State::READY; }
|
||||||
|
bool isHooked() const { return hooked; }
|
||||||
|
|
||||||
|
template <auto Function, typename C> void onLoaded(C* instance) {
|
||||||
|
callback.bind<Function>(instance);
|
||||||
|
if (isReady()) (instance->*Function)(State::READY, State::READY, *this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Resource(const Path& path, ResourceTypeManager& manager);
|
||||||
|
|
||||||
|
virtual void onReadying() {}
|
||||||
|
virtual void unload() = 0;
|
||||||
|
virtual bool load(size_t size, const uint8_t* mem) = 0;
|
||||||
|
|
||||||
|
void onCreated(State newState);
|
||||||
|
void performUnload();
|
||||||
|
void addDependency(Resource& dependent);
|
||||||
|
void removeDependency(Resource& dependent);
|
||||||
|
void checkState();
|
||||||
|
void refresh();
|
||||||
|
|
||||||
|
State desiredState;
|
||||||
|
uint16_t emptyDependencies;
|
||||||
|
ResourceTypeManager& manager;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void doLoad();
|
||||||
|
void fileLoaded(size_t fileSize, const uint8_t* mem, bool success);
|
||||||
|
void stateChanged(State old, State newState, Resource&);
|
||||||
|
|
||||||
|
Resource(const Resource&) = delete;
|
||||||
|
void operator=(const Resource&) = delete;
|
||||||
|
|
||||||
|
Observer callback;
|
||||||
|
size_t size;
|
||||||
|
Path path;
|
||||||
|
uint32_t references;
|
||||||
|
uint16_t failedDependencies;
|
||||||
|
FileSystem::AsyncHandle handle;
|
||||||
|
State state;
|
||||||
|
bool hooked = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PrefabResource : Resource {
|
||||||
|
PrefabResource(const Path& path, ResourceTypeManager& resource_manager);
|
||||||
|
ResourceType getType() const override;
|
||||||
|
void unload() override;
|
||||||
|
bool load(size_t size, const uint8_t* data) override;
|
||||||
|
|
||||||
|
OutputMemoryStream data;
|
||||||
|
StableHash hash;
|
||||||
|
static const ResourceType TYPE;
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,194 @@
|
||||||
|
#include "ResourceManager.h"
|
||||||
|
#include "Resource.h"
|
||||||
|
#include "spdlog/spdlog.h"
|
||||||
|
|
||||||
|
namespace ShadowEngine {
|
||||||
|
|
||||||
|
void ResourceTypeManager::create(struct ResourceType type, struct ResourceManager &manager) {
|
||||||
|
manager.add(type, this);
|
||||||
|
owner = &manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceTypeManager::destroy() {
|
||||||
|
for (auto iter = resources.begin(), end = resources.end(); iter != end; ++iter) {
|
||||||
|
Resource* res = iter->second;
|
||||||
|
if (!res->isEmpty())
|
||||||
|
spdlog::error("Resource Type Manager destruction leaks ", res->path.get());
|
||||||
|
|
||||||
|
destroyResource(*res);
|
||||||
|
}
|
||||||
|
resources.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
Resource* ResourceTypeManager::get(const Path& path) {
|
||||||
|
auto it = resources.find(path.getHash());
|
||||||
|
if (it != resources.end()) return it->second;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Resource* ResourceTypeManager::load(const Path &path) {
|
||||||
|
if (path.isEmpty()) return nullptr;
|
||||||
|
Resource* res = get(path);
|
||||||
|
if (res == nullptr) {
|
||||||
|
res = createResource(path);
|
||||||
|
resources[path.getHash()] = res;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res->isEmpty() && res->desiredState == Resource::State::EMPTY) {
|
||||||
|
if (owner->onLoad(*res) == ResourceManager::LoadHook::Action::DEFERRED) {
|
||||||
|
res->hooked = true;
|
||||||
|
res->desiredState = Resource::State::READY;
|
||||||
|
res->increaseReferences();
|
||||||
|
res->increaseReferences();
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
res->doLoad();
|
||||||
|
}
|
||||||
|
|
||||||
|
res->increaseReferences();
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceTypeManager::removeUnreferencedResources() {
|
||||||
|
if (!unloadEnabled) return;
|
||||||
|
|
||||||
|
std::vector<Resource*> toRemove;
|
||||||
|
for (auto i : resources)
|
||||||
|
if (i.second->getReferenceCount() == 0) toRemove.push_back(i.second);
|
||||||
|
|
||||||
|
for (auto i : toRemove) {
|
||||||
|
auto iter = resources.find(i->getPath().getHash());
|
||||||
|
if (iter->second->isReady()) iter->second->performUnload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceTypeManager::reload(const Path &path) {
|
||||||
|
Resource* res = get(path);
|
||||||
|
if (res) reload(*res);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceTypeManager::reload(Resource& res) {
|
||||||
|
if (res.state != Resource::State::EMPTY)
|
||||||
|
res.performUnload();
|
||||||
|
else if (res.desiredState == Resource::State::READY)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (owner->onLoad(res) == ResourceManager::LoadHook::Action::DEFERRED) {
|
||||||
|
res.hooked = true;
|
||||||
|
res.desiredState = Resource::State::READY;
|
||||||
|
res.increaseReferences();
|
||||||
|
res.increaseReferences();
|
||||||
|
} else {
|
||||||
|
res.performUnload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceTypeManager::setUnloadable(bool status) {
|
||||||
|
unloadEnabled = status;
|
||||||
|
if (!unloadEnabled) return;
|
||||||
|
|
||||||
|
for (auto res : resources)
|
||||||
|
if (res.second->getReferenceCount() == 0)
|
||||||
|
res.second->performUnload();
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceTypeManager::ResourceTypeManager() :
|
||||||
|
resources(),
|
||||||
|
owner(nullptr),
|
||||||
|
unloadEnabled(true) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceTypeManager::~ResourceTypeManager() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceManager::ResourceManager() :
|
||||||
|
managers(),
|
||||||
|
hook(nullptr),
|
||||||
|
filesystem(nullptr) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceManager::~ResourceManager() = default;
|
||||||
|
|
||||||
|
void ResourceManager::init(FileSystem &fs) {
|
||||||
|
filesystem = &fs;
|
||||||
|
}
|
||||||
|
|
||||||
|
Resource* ResourceManager::load(ResourceType type, const Path& path) {
|
||||||
|
ResourceTypeManager* manager = get(type);
|
||||||
|
if (!manager) return nullptr;
|
||||||
|
return load(*manager, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
Resource* ResourceManager::load(ResourceTypeManager& manager, const Path& path) {
|
||||||
|
return manager.load(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceTypeManager* ResourceManager::get(ResourceType type) {
|
||||||
|
auto iter = managers.find(type);
|
||||||
|
if (iter == managers.end()) return nullptr;
|
||||||
|
return iter->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceManager::LoadHook::continueLoad(Resource &res) {
|
||||||
|
res.decreaseReferences();
|
||||||
|
res.hooked = false;
|
||||||
|
res.desiredState = Resource::State::EMPTY;
|
||||||
|
res.doLoad();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceManager::setLoadHook(LoadHook *loadHook) {
|
||||||
|
hook = loadHook;
|
||||||
|
|
||||||
|
if (hook)
|
||||||
|
for (auto manager : managers)
|
||||||
|
for (auto res : manager.second->getResources())
|
||||||
|
if (res.second->isFailure())
|
||||||
|
manager.second->reload(*res.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceManager::LoadHook::Action ResourceManager::onLoad(Resource &res) const {
|
||||||
|
return hook ? hook->load(res) : LoadHook::Action::IMMEDIATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceManager::add(ResourceType type, ResourceTypeManager* manager) {
|
||||||
|
managers[type] = manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceManager::remove(ResourceType type) {
|
||||||
|
managers.erase(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceManager::removeUnreferenced() {
|
||||||
|
for (auto manager : managers)
|
||||||
|
manager.second->removeUnreferencedResources();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceManager::setUnloadable(bool enable) {
|
||||||
|
for (auto manager : managers)
|
||||||
|
manager.second->setUnloadable(enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceManager::reloadAll() {
|
||||||
|
while (filesystem->hasWork()) filesystem->processCallbacks();
|
||||||
|
|
||||||
|
std::vector<Resource*> toReload;
|
||||||
|
for (auto manager : managers) {
|
||||||
|
ResourceTypeManager::ResourceTable& resources = manager.second->getResources();
|
||||||
|
for (auto res : resources) {
|
||||||
|
if (res.second->isReady()) {
|
||||||
|
res.second->performUnload();
|
||||||
|
toReload.push_back(res.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceManager::reload(const Path& path) {
|
||||||
|
for (auto manager : managers)
|
||||||
|
manager.second->reload(path);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
#pragma once
|
||||||
|
#include <map>
|
||||||
|
#include <fs/hash.h>
|
||||||
|
#include <fs/path.h>
|
||||||
|
|
||||||
|
namespace ShadowEngine {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles all of the Resources of a single Type.
|
||||||
|
* Handles reference counting, hot reloading, and etc.
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct ResourceTypeManager {
|
||||||
|
friend struct Resource;
|
||||||
|
friend struct ResourceManager;
|
||||||
|
|
||||||
|
using ResourceTable = std::map<PathHash, struct Resource*>;
|
||||||
|
|
||||||
|
void create(struct ResourceType type, struct ResourceManager& manager);
|
||||||
|
void destroy();
|
||||||
|
|
||||||
|
void setUnloadable(bool status);
|
||||||
|
|
||||||
|
void removeUnreferencedResources();
|
||||||
|
|
||||||
|
void reload(const Path& path);
|
||||||
|
void reload(Resource& resource);
|
||||||
|
|
||||||
|
ResourceTable& getResources() { return resources; }
|
||||||
|
|
||||||
|
ResourceTypeManager();
|
||||||
|
virtual ~ResourceTypeManager();
|
||||||
|
ResourceManager& getOwner() const { return *owner; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Resource* load(const Path& path);
|
||||||
|
virtual Resource* createResource(const Path& path) = 0;
|
||||||
|
virtual void destroyResource(Resource& res) = 0;
|
||||||
|
Resource* get(const Path& path);
|
||||||
|
|
||||||
|
ResourceTable resources;
|
||||||
|
ResourceManager* owner;
|
||||||
|
bool unloadEnabled;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles all of the ResourceTypeManagers, for every ResourceType with at least one applicable Resource
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct ResourceManager {
|
||||||
|
using ResourceTypeManagers = std::map<ResourceType, ResourceTypeManager*>;
|
||||||
|
|
||||||
|
struct LoadHook {
|
||||||
|
enum class Action { IMMEDIATE, DEFERRED };
|
||||||
|
virtual ~LoadHook();
|
||||||
|
virtual Action load(Resource& res) = 0;
|
||||||
|
void continueLoad(Resource& res);
|
||||||
|
};
|
||||||
|
|
||||||
|
ResourceManager();
|
||||||
|
~ResourceManager();
|
||||||
|
ResourceManager(const ResourceManager& o) = delete;
|
||||||
|
|
||||||
|
void init(struct FileSystem& fs);
|
||||||
|
|
||||||
|
ResourceTypeManager* get(ResourceType);
|
||||||
|
const ResourceTypeManagers& getAll() const { return managers; }
|
||||||
|
|
||||||
|
template <typename R>
|
||||||
|
R* load(const Path& path) {
|
||||||
|
return static_cast<R*>(load(R::TYPE, path));
|
||||||
|
}
|
||||||
|
|
||||||
|
Resource* load(ResourceTypeManager& manager, const Path& path);
|
||||||
|
Resource* load(ResourceType type, const Path& path);
|
||||||
|
|
||||||
|
void setLoadHook(LoadHook* hook);
|
||||||
|
bool isHooked() const { return hook; }
|
||||||
|
LoadHook::Action onLoad(Resource& res) const;
|
||||||
|
void add(ResourceType, ResourceTypeManager* manager);
|
||||||
|
void remove(ResourceType type);
|
||||||
|
void reload(const Path& path);
|
||||||
|
void reloadAll();
|
||||||
|
void removeUnreferenced();
|
||||||
|
void setUnloadable(bool enable);
|
||||||
|
|
||||||
|
FileSystem& getFileSystem() { return *filesystem; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
ResourceTypeManagers managers;
|
||||||
|
FileSystem* filesystem;
|
||||||
|
LoadHook* hook;
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
11
projs/shadow/shadow-engine/shadow-assets/src/str/string.cpp
Normal file
11
projs/shadow/shadow-engine/shadow-assets/src/str/string.cpp
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
#include <str/string.h>
|
||||||
|
|
||||||
|
namespace ShadowEngine::Str {
|
||||||
|
std::string toLower(const std::string& str) {
|
||||||
|
std::string temp;
|
||||||
|
for (auto c : str) {
|
||||||
|
temp.append(std::to_string(tolower(c)));
|
||||||
|
}
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
}
|
12
projs/shadow/shadow-engine/shadow-assets/src/str/string.h
Normal file
12
projs/shadow/shadow-engine/shadow-assets/src/str/string.h
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace ShadowEngine {
|
||||||
|
// String manipluation utilities.
|
||||||
|
// Because std::string is heavily lacking.
|
||||||
|
namespace Str {
|
||||||
|
// Convert the string to lower case, return a new string.
|
||||||
|
// This only works in ASCII encoding.
|
||||||
|
std::string toLower(std::string& str);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,92 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include <cstdint>
|
|
||||||
#include "id/ID.h"
|
|
||||||
|
|
||||||
namespace SE {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Component is a part of an Entity that stores and manages data.
|
|
||||||
* This data is held separately from the Entity that requests it.
|
|
||||||
*
|
|
||||||
* Components can be Spatial (ie. Position, Rotation).
|
|
||||||
* Components can also be Visual (ie. Mesh, Collider).
|
|
||||||
* Components are part of the larger Entity-Component-System architecture.
|
|
||||||
*
|
|
||||||
* Systems operate over these Components.
|
|
||||||
* Entities may have their own Systems, and each Scene has its own set of global Systems.
|
|
||||||
* Components may be registered to only one of these.
|
|
||||||
*
|
|
||||||
* EntityComponent classes are not singleton; new EntityComponent(..) is valid.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
class EntityComponent {
|
|
||||||
public:
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Component's possible statuses.
|
|
||||||
*/
|
|
||||||
enum class Status: uint8_t {
|
|
||||||
Unloaded, // The component is not yet loaded.
|
|
||||||
Loading, // The component is being loaded.
|
|
||||||
Loaded, // The component is finished loading, but is not yet populated with data.
|
|
||||||
Failed, // The component encountered an error while loading.
|
|
||||||
Initialized // The component is finished loading, and is populated with data.
|
|
||||||
};
|
|
||||||
|
|
||||||
virtual ~EntityComponent();
|
|
||||||
|
|
||||||
// Get the ComponentID.
|
|
||||||
inline ComponentID const& GetID() const { return id; }
|
|
||||||
// Get the Name of this Component; guaranteed to be unique in a given entity.
|
|
||||||
inline std::string& GetName() { return name; }
|
|
||||||
// Get the Entity that owns this Component.
|
|
||||||
inline EntityID const& GetParent() const { return owner; }
|
|
||||||
|
|
||||||
inline Status GetStatus() const { return status; }
|
|
||||||
// Check Status
|
|
||||||
inline bool IsLoaded() const { return status == Status::Loaded; }
|
|
||||||
inline bool IsLoading() const { return status == Status::Loading; }
|
|
||||||
inline bool IsUnloaded() const { return status == Status::Unloaded; }
|
|
||||||
inline bool Failed() const { return status == Status::Failed; }
|
|
||||||
inline bool Initialized() const { return status == Status::Initialized; }
|
|
||||||
|
|
||||||
// Whether one instance of this Component is allowed per Entity
|
|
||||||
virtual bool IsSingleton() const { return false; }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
|
|
||||||
EntityComponent() = default;
|
|
||||||
EntityComponent(std::string& name) : name(name) {}
|
|
||||||
|
|
||||||
// Load Component data. May perform background ops.
|
|
||||||
virtual void Load() = 0;
|
|
||||||
// Unload Component data. Must be blocking.
|
|
||||||
virtual void Unload() = 0;
|
|
||||||
|
|
||||||
// Check that everything went properly.
|
|
||||||
virtual void UpdateStatus() = 0;
|
|
||||||
|
|
||||||
// Update the status to Initialized. Must only be called if checks passed.
|
|
||||||
// The status must be Loaded.
|
|
||||||
virtual void Initialize() { status = Status::Initialized; }
|
|
||||||
|
|
||||||
// Prepare for unloading the Component. Must be called before the process begins.
|
|
||||||
// The status must be Initialized.
|
|
||||||
virtual void Close() { status == Status::Loaded; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
// This Component's unique ID.
|
|
||||||
ComponentID id = ComponentID::Generate();
|
|
||||||
// The Entity that requested this Component.
|
|
||||||
EntityID owner;
|
|
||||||
// The name of the Component, for visualization
|
|
||||||
std::string name;
|
|
||||||
// The status of the Component
|
|
||||||
Status status = Status::Unloaded;
|
|
||||||
|
|
||||||
// Whether this Component is registered to the Entity's Local Systems.
|
|
||||||
bool localComponent = false;
|
|
||||||
// Whether this Component is registered to the Scene's Global Systems.
|
|
||||||
bool globalComponent = false;
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,130 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include <array>
|
|
||||||
|
|
||||||
namespace SE {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An Entity Component that has a Spatial context.
|
|
||||||
* AKA, a Component with a position, a rotation, and a size.
|
|
||||||
*
|
|
||||||
* Spatial Components may exist in a hierarchy; where each subsequent object inherits the spatial positions of its' parents.
|
|
||||||
* This allows for things like complex articulation of large machines, etc.
|
|
||||||
*/
|
|
||||||
class EntitySpatialComponent : public EntityComponent {
|
|
||||||
public:
|
|
||||||
|
|
||||||
// Whether this is the root spatial component for this Entity.
|
|
||||||
inline bool IsSpatialRoot() const { return parent == nullptr; }
|
|
||||||
|
|
||||||
// Fetch the transforms and bounds.
|
|
||||||
inline Math::Transform const& GetLocalTransform() const { return transform; }
|
|
||||||
inline Math::OrientedBB const& GetLocalBounds() const { return bounds; }
|
|
||||||
|
|
||||||
inline Math::Transform const& GetWorldTransform() const { return worldTransform; }
|
|
||||||
inline Math::OrientedBB const& GetWorldBounds() const { return worldBounds; }
|
|
||||||
|
|
||||||
// Get the position in world-space.
|
|
||||||
inline Math::Vector const& GetPosition() const { return worldTransform.GetTranslation(); }
|
|
||||||
|
|
||||||
// Get the rotation in world-space.
|
|
||||||
inline Math::Quaternion const& GetRotation() const { return worldTransform.GetRotation(); }
|
|
||||||
|
|
||||||
// Get vectors relative to this spatial component.
|
|
||||||
inline Math::Vector GetForwardVector() const { return worldTransform.GetForwardVector(); }
|
|
||||||
inline Math::Vector GetUpVector() const { return worldTransform.GetUpVector(); }
|
|
||||||
inline Math::Vector GetRightVector() const { return worldTransform.GetRightVector(); }
|
|
||||||
|
|
||||||
// Update the local and world transforms
|
|
||||||
inline void SetLocalTransform(Math::Transform const& newTransform);
|
|
||||||
inline void SetWorldTransform(Math::Transform const& newTransform);
|
|
||||||
|
|
||||||
// Move this component by the specified amount of transform, rotation
|
|
||||||
inline void MoveBy(Math::Transform const& delta);
|
|
||||||
|
|
||||||
|
|
||||||
// Is there a child spatial component
|
|
||||||
inline bool HasChildren() const { return !children.empty(); }
|
|
||||||
|
|
||||||
// Get the ID of the parent of this Spatial Component
|
|
||||||
inline ComponentID const& GetParentID() const { return parent->GetID(); }
|
|
||||||
|
|
||||||
// Get the transform of the parent component
|
|
||||||
inline Math::Transform const& GetParentTransform() const { return parent->worldTransform; }
|
|
||||||
|
|
||||||
// Fetch how deep in the spatial hierarchy this Component is.
|
|
||||||
size_t GetHierarchyDepth(bool singleEntity = true) const;
|
|
||||||
|
|
||||||
// Check if we are a child of a given Component
|
|
||||||
bool IsChildOf(EntitySpatialComponent const* parent) const;
|
|
||||||
|
|
||||||
// Get the ID of the socket we are attached to
|
|
||||||
inline std::string const& GetAttachmentSocketID() const { return attachmentSocket; }
|
|
||||||
|
|
||||||
// Set the name of the socket we wish to be attached to.
|
|
||||||
inline void SetAttachmentSocket(std::string& id) { attachmentSocket = id;}
|
|
||||||
|
|
||||||
// Fetch the transform of a specific attachment.
|
|
||||||
Math::Transform const& GetAttachmentTransform(std::string& id) const;
|
|
||||||
|
|
||||||
// Whether this Component supports local scaling
|
|
||||||
virtual bool LocalScale() const { return false; }
|
|
||||||
// Fetch the local scale, if supported. { 1, 1, 1 } otherwise.
|
|
||||||
virtual std::array<float, 3> const& GetLocalScale() const { static auto ones = std::array<float, 3> { 1, 1, 1 }; return ones; }
|
|
||||||
|
|
||||||
// Convert a given world-space transform to a model-space transform.
|
|
||||||
inline Math::Transform const& ConvertWorldToLocalTransform(Math::Transform const& world);
|
|
||||||
|
|
||||||
|
|
||||||
protected:
|
|
||||||
|
|
||||||
virtual void Initialize() override;
|
|
||||||
|
|
||||||
// Set the local scale, if supported
|
|
||||||
virtual void SetLocalScale(std::array<float, 3> const& newScale) {}
|
|
||||||
|
|
||||||
// Calculate the local bounding box for the Component; the position should always be close to origin.
|
|
||||||
virtual Math::OrientedBB CalculateBounds() const;
|
|
||||||
|
|
||||||
// Update the local and world bounds for the Component.
|
|
||||||
void UpdateBounds();
|
|
||||||
|
|
||||||
// Attempt to find the given Socket in this Component, and return its' Transform if present.
|
|
||||||
bool GetAttachmentTransform(std::string& socketId, Math::Transform& out) const;
|
|
||||||
virtual bool FindAttachmentTransform(std::string& socketId, Math::Transform& out) const;
|
|
||||||
|
|
||||||
// Whether this Component has the given Socket.
|
|
||||||
// Default: No sockets.
|
|
||||||
virtual bool HasSocket(std::string& socket) const { return false;}
|
|
||||||
|
|
||||||
// Process the creation/destruction/movement of a Socket.
|
|
||||||
void UpdateSockets();
|
|
||||||
|
|
||||||
// Called whenever the World Transform is updated.
|
|
||||||
virtual void OnMoved() {}
|
|
||||||
|
|
||||||
// Set the World Transform for a component.
|
|
||||||
// Skips a lot of internal processing.
|
|
||||||
inline void SetWorldTransform(Math::Transform& newTransform, bool callback = true);
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
inline void CalculateWorldTransform(bool callback = true);
|
|
||||||
|
|
||||||
// The location of this Component, relative to the world.
|
|
||||||
Math::Transform transform;
|
|
||||||
// The collision boundary of this Component.
|
|
||||||
Math::OrientedBB bounds;
|
|
||||||
|
|
||||||
// The world transform of this component; how to get to the position from the world origin.
|
|
||||||
Math::Transform worldTransform;
|
|
||||||
Math::OrientedBB worldBounds;
|
|
||||||
|
|
||||||
// The parent of this Spatial Component
|
|
||||||
EntitySpatialComponent* parent = nullptr;
|
|
||||||
// The socket (attachment point) of the parent entity that this Component is attached to.
|
|
||||||
std::string attachmentSocket;
|
|
||||||
// The Components that are attached to this Component.
|
|
||||||
std::vector<EntitySpatialComponent*> children;
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <id/UUID.h> // Shadow-Engine/core/inc/id
|
|
||||||
|
|
||||||
namespace SE {
|
|
||||||
|
|
||||||
// An ID for a section of the scene (which may be unloaded separately of the level; for ie. open world Cells.)
|
|
||||||
using EntitySectionID = UUID;
|
|
||||||
// An ID for a scene (which contains all sections and entities).
|
|
||||||
using EntitySceneID = UUID;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contains the common data and functions for the Entity System's identifiers.
|
|
||||||
*/
|
|
||||||
struct IDContainer {
|
|
||||||
public:
|
|
||||||
IDContainer() = default;
|
|
||||||
explicit IDContainer(uint64_t v) : id(v) {}
|
|
||||||
|
|
||||||
// Check if the ID is valid (non-zero)
|
|
||||||
__inline virtual bool Valid() const { return id != 0; }
|
|
||||||
// Set this ID to be invalid (zero).
|
|
||||||
__inline virtual void Invalidate() { id = 0; }
|
|
||||||
// Check for in/equality against another ID.
|
|
||||||
__inline virtual bool operator==(IDContainer const& other) const { return id == other.id; }
|
|
||||||
__inline virtual bool operator!=(IDContainer const& other) const { return id != other.id; }
|
|
||||||
__inline virtual bool operator<(IDContainer const& other) const { return id < other.id; }
|
|
||||||
|
|
||||||
uint64_t id;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An ID used for an Entity.
|
|
||||||
* Is only guaranteed to be unique in the current scene (ie. a level change may also change the IDs of the entities within).
|
|
||||||
*/
|
|
||||||
struct EntityID : public IDContainer {
|
|
||||||
/**
|
|
||||||
* @return a new, unused Entity ID.
|
|
||||||
*/
|
|
||||||
static EntityID Generate();
|
|
||||||
|
|
||||||
EntityID() = default;
|
|
||||||
explicit EntityID(uint64_t v) : IDContainer(v) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An ID used for a Component (a segment of data attached to an Entity).
|
|
||||||
* Is only guaranteed to be unique in the current scene (ie. a level change may also change the IDs of the components within).
|
|
||||||
*/
|
|
||||||
struct ComponentID : public IDContainer {
|
|
||||||
/**
|
|
||||||
* @return a new, unused Component ID.
|
|
||||||
*/
|
|
||||||
static ComponentID Generate();
|
|
||||||
|
|
||||||
ComponentID() = default;
|
|
||||||
explicit ComponentID(uint64_t v) : IDContainer(v) {}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,172 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <scene/EntitySection.h>
|
|
||||||
|
|
||||||
namespace SE {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A scene is a collection of Sections that contain Entities.
|
|
||||||
* Scenes have support for asynchronous task management.
|
|
||||||
* Scenes have their own input state, local to the world.
|
|
||||||
* Scenes have Systems that operate over all Scene Components of all entities within.
|
|
||||||
* Scenes can be either for Tools or for Gameplay.
|
|
||||||
*
|
|
||||||
* Scenes each have a primary viewport and any number of secondary viewports.
|
|
||||||
*
|
|
||||||
* Scenes contain Sections, which contain Entities.
|
|
||||||
* One of the Sections in the Scene is always loaded; similar to Spawn Chunks.
|
|
||||||
*
|
|
||||||
* Scenes have a local timescale, which can be increased to make physics and logic run faster.
|
|
||||||
*/
|
|
||||||
class EntityScene {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The type of the scene.
|
|
||||||
* More-or-less exists to allow differentiating between an Editor and a Runtime.
|
|
||||||
*/
|
|
||||||
enum Type {
|
|
||||||
TOOLS, // This scene exists for tooling support, ie. the builtin editor.
|
|
||||||
GAME // This scene exists for gameplay, ie. the runtime.
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
EntityScene(Type wType = Type::GAME);
|
|
||||||
~EntityScene();
|
|
||||||
|
|
||||||
// Fetch the ID of this scene.
|
|
||||||
inline EntitySceneID const& GetID() const { return id; }
|
|
||||||
// Whether this is a Gameplay scene.
|
|
||||||
inline bool IsGameScene() const { return type == Type::GAME; }
|
|
||||||
|
|
||||||
// Prepare the Scene for processing entity updates. TODO: Systems.
|
|
||||||
void Initialize();
|
|
||||||
// Prepare all Regions and Entities for the closing of the Scene.
|
|
||||||
void Shutdown();
|
|
||||||
|
|
||||||
|
|
||||||
// Whether or not updates are suspended. ( Game is Paused )
|
|
||||||
inline bool IsSuspended() const { return suspended; }
|
|
||||||
|
|
||||||
// Stop all Regions, Entities and Systems from updating.
|
|
||||||
void Suspend() { suspended = true; }
|
|
||||||
|
|
||||||
// Resume updates to the Entities and Systems within.
|
|
||||||
void Resume() { suspended = false; }
|
|
||||||
|
|
||||||
// Run a single tick of Entity and System updates. TODO: context
|
|
||||||
void Update();
|
|
||||||
|
|
||||||
// Handle Entities, Regions, Resources that want to load or unload.
|
|
||||||
void UpdateLoads();
|
|
||||||
|
|
||||||
// TODO: Systems
|
|
||||||
|
|
||||||
// TODO: Input
|
|
||||||
|
|
||||||
|
|
||||||
// Check whether time is paused; the Scene itself can be unpaused during this, to allow movement through a paused world.
|
|
||||||
inline bool IsTimePaused() const { return timeScale <= 0.0f; }
|
|
||||||
|
|
||||||
// Pause time in the Scene.
|
|
||||||
inline void PauseTime() { timeScale = 0.0f; }
|
|
||||||
|
|
||||||
// The Time Scale for the Scene
|
|
||||||
inline float GetTimeScale() const { return timeScale; }
|
|
||||||
|
|
||||||
// Set the Time Scale for the Scene.
|
|
||||||
inline void SetTimeScale(float newScale) { timeScale = newScale; }
|
|
||||||
|
|
||||||
// Request a Time Step through a paused world.
|
|
||||||
inline void TimeStep() { timeStepNeeded = true; }
|
|
||||||
|
|
||||||
// Whether a Scene with Paused Time needs a time step
|
|
||||||
inline bool TimeStepRequested() const { return timeStepNeeded; }
|
|
||||||
|
|
||||||
// How long a single Time Step last, at most.
|
|
||||||
inline float TimeStepLength() const { return timeStepLength; }
|
|
||||||
|
|
||||||
// Set the maximum length of a Time Step.
|
|
||||||
inline void SetTimeStepLength(float newStep) { timeStepLength = newStep; }
|
|
||||||
|
|
||||||
// TODO: Viewports
|
|
||||||
|
|
||||||
// Fetch the Section that is always present and loaded; be careful adding things to this.
|
|
||||||
EntitySection* GetPersistentSection() { return sections[0]; }
|
|
||||||
EntitySection const* GetPersistentSection() const { return sections[0]; }
|
|
||||||
|
|
||||||
// Get the first Section that is not persistent, if present.
|
|
||||||
EntitySection* GetFirstNonPersistentSection() { return sections.size() > 1 ? sections[1] : nullptr; }
|
|
||||||
EntitySection const* GetFirstNonPersistentSection() const { return sections.size() > 1 ? sections[1] : nullptr; }
|
|
||||||
|
|
||||||
// Create a transient (memory-resident) Section.
|
|
||||||
EntitySection* CreateTransientSection();
|
|
||||||
|
|
||||||
// Get a Section from memory.
|
|
||||||
EntitySection const* GetSection(EntitySectionID const& sID) const;
|
|
||||||
EntitySection* GetSection(EntitySectionID const& sID);
|
|
||||||
|
|
||||||
// Whether anything is loading into a Section
|
|
||||||
bool IsLoading() const;
|
|
||||||
|
|
||||||
// Whether a Section exists in this scene
|
|
||||||
bool HasSection(const EntitySectionID& eID) const;
|
|
||||||
// Whether the Section in this scene is loaded.
|
|
||||||
bool isSectionLoaded(const EntitySectionID& sID) const;
|
|
||||||
|
|
||||||
// Load a Section from the Scene file into memory.
|
|
||||||
EntitySectionID LoadSection(EntitySectionID& section);
|
|
||||||
// Unload a section from memory; delete every entity within.
|
|
||||||
void UnloadSection(EntitySectionID& section);
|
|
||||||
|
|
||||||
inline Entity* FindEntity(EntityID& entity) const {
|
|
||||||
Entity* e = nullptr;
|
|
||||||
for (auto const& section : sections) {
|
|
||||||
e = section->GetEntity(entity);
|
|
||||||
if (e != nullptr) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
// The ID of this Scene.
|
|
||||||
EntitySceneID id = UUID::Generate();
|
|
||||||
|
|
||||||
// The system that manages asynchronous background tasks for this Scene, such as Systems and Entity management.
|
|
||||||
// Tasks* tasks;
|
|
||||||
|
|
||||||
// The local Input State, which exists relative to the viewport, allowing for tri picking.
|
|
||||||
// Input input;
|
|
||||||
|
|
||||||
// TODO: Loading/Initialization
|
|
||||||
|
|
||||||
// TODO: Scene Systems
|
|
||||||
|
|
||||||
// The type of this Scene; default to game.
|
|
||||||
Type type = Type::GAME;
|
|
||||||
|
|
||||||
// Whether or not everything in the map is ready to start.
|
|
||||||
bool initialized = false;
|
|
||||||
// Whether or not everything in the map is paused; no logic runs, nothing updates.
|
|
||||||
bool suspended = false;
|
|
||||||
|
|
||||||
// TODO: Viewport
|
|
||||||
|
|
||||||
// The list of Sections in this Scene.
|
|
||||||
std::vector<EntitySection*> sections;
|
|
||||||
|
|
||||||
// Every entity that is eligible for updates.
|
|
||||||
// Ie. Every Initialized Entity in every Loaded Section.
|
|
||||||
std::vector<Entity*> entityUpdateList;
|
|
||||||
|
|
||||||
//TODO: Scene Systems 2
|
|
||||||
|
|
||||||
// The multiplier for the number of steps per second (1/timescale * stepInterval)
|
|
||||||
float timeScale = 1.0f;
|
|
||||||
// The maximum length of each time step; if a step goes over this, it is aborted and the next begins processing.
|
|
||||||
float timeStepLength = 1.0f/30;
|
|
||||||
// Whether or not a new Time Step should begin immediately. Only applicable to Scenes with Paused Time.
|
|
||||||
bool timeStepNeeded = false;
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,132 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include <vector>
|
|
||||||
#include <map>
|
|
||||||
|
|
||||||
namespace SE {
|
|
||||||
// Forward declaration, see inc/entity/Entity.h
|
|
||||||
class Entity;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A section is a collection of Entities with linked runtime.
|
|
||||||
* ie. a game level could have one giant linked Section.
|
|
||||||
* a cell of an open-world game could represent one Region,
|
|
||||||
* and rooms of a BSP level could represent one Region.
|
|
||||||
*/
|
|
||||||
class EntitySection {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The possible statuses for this Section.
|
|
||||||
* Entities are not valid until the Loaded status is reached.
|
|
||||||
*/
|
|
||||||
enum class Status {
|
|
||||||
Failed = -1, // An error occurred during loading.
|
|
||||||
Unloaded = 0, // The Section is present, but none of the data is populated.
|
|
||||||
Loading, // The Section is being loaded with data.
|
|
||||||
Loaded, // The Section is present and contains valid data.
|
|
||||||
Unloading // The Section is present but preparing to unload.
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request the deletion of an entity, after ie. destruction or death.
|
|
||||||
*/
|
|
||||||
struct PendingRemoval {
|
|
||||||
PendingRemoval(Entity* entity, bool destroy)
|
|
||||||
: entity(entity), destroy(destroy) {}
|
|
||||||
|
|
||||||
Entity* entity = nullptr;
|
|
||||||
bool destroy = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
EntitySection();
|
|
||||||
EntitySection(EntitySection const& section);
|
|
||||||
EntitySection(EntitySection&& section);
|
|
||||||
~EntitySection();
|
|
||||||
|
|
||||||
EntitySection& operator=(EntitySection const& section);
|
|
||||||
EntitySection& operator=(EntitySection&& section);
|
|
||||||
|
|
||||||
// Get the ID of this Section
|
|
||||||
inline EntitySectionID GetID() const { return id; }
|
|
||||||
// Whether or not this Section was generated, without being loaded from disk.
|
|
||||||
inline bool IsTransient() const { return isTransient; }
|
|
||||||
|
|
||||||
// TODO: Contexts?
|
|
||||||
void Load();
|
|
||||||
void Unload();
|
|
||||||
|
|
||||||
// Check that everything went properly, and update state accordingly.
|
|
||||||
bool UpdateState();
|
|
||||||
|
|
||||||
// Whether there are any pending entity removals or additions.
|
|
||||||
inline bool HasPendingEntityChanges() const { return (entitiesPendingLoading.size() + entitiesPendingRemoval.size()) > 0; }
|
|
||||||
|
|
||||||
bool IsLoading() const { return status == Status::Loading; }
|
|
||||||
inline bool IsLoaded() const { return status == Status::Loaded; }
|
|
||||||
inline bool IsUnloaded() const { return status == Status::Unloaded; }
|
|
||||||
inline bool LoadingFailed() const { return status == Status::Failed; }
|
|
||||||
|
|
||||||
// Get the total number of entities in the section
|
|
||||||
inline size_t GetEntityCount() const { return entities.size(); }
|
|
||||||
// Get all entities in the region.
|
|
||||||
inline std::vector<Entity*> const& GetEntities() const { return entities; }
|
|
||||||
// Get a specific entity by its' ID.
|
|
||||||
inline Entity* GetEntity(const EntityID& eID) const {
|
|
||||||
auto i = entityLookup.find(eID);
|
|
||||||
return (i != entityLookup.end()) ? i->second : nullptr;
|
|
||||||
}
|
|
||||||
// Check whether this region contains the specified entity.
|
|
||||||
inline bool ContainsEntity(const EntityID& eID) const { return GetEntity(eID) != nullptr; }
|
|
||||||
|
|
||||||
// Add all of the specified entities to the region.
|
|
||||||
// Transfers ownership if already present.
|
|
||||||
// This operation takes time, but is guaranteed to be finished by the start of the next frame.
|
|
||||||
void AddEntities(const std::vector<Entity*>& entities);
|
|
||||||
|
|
||||||
// Add an entity to the region.
|
|
||||||
// Transfers ownership if already present.
|
|
||||||
void AddEntity(Entity* entity);
|
|
||||||
|
|
||||||
// Queue an entity for removal from this region.
|
|
||||||
// Transfers ownership to the caller.
|
|
||||||
// This operation takes time, and no guarantee is given for completion.
|
|
||||||
Entity* RemoveEntity(const EntityID& eID);
|
|
||||||
|
|
||||||
// Queue an entity for unloading from this region.
|
|
||||||
// This operation takes time, and no guarantee is given for completion.
|
|
||||||
void DestroyEntity(const EntityID& eID);
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
// A callback for when an entity's state changes (ie. when loading)
|
|
||||||
void EntityStateUpdated(Entity* entity);
|
|
||||||
|
|
||||||
// TODO: Process Loading, Unloading, Entity Shutdown, Entity Removal, Entity Loading.
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
EntitySectionID id = UUID::Generate();
|
|
||||||
Status status = Status::Unloaded;
|
|
||||||
|
|
||||||
// Transient = created on-the-fly, rather than existing on disk.
|
|
||||||
bool const isTransient = false;
|
|
||||||
|
|
||||||
// The list of every entity in the Section, including those that are not yet loaded, or those that are due to be unloaded.
|
|
||||||
std::vector<Entity*> entities;
|
|
||||||
|
|
||||||
// A fast map for looking up entities by ID.
|
|
||||||
std::map<EntityID, Entity*> entityLookup;
|
|
||||||
|
|
||||||
// The list of entities that are currently being initialized to be put into the Section.
|
|
||||||
std::vector<Entity*> entitiesLoading;
|
|
||||||
|
|
||||||
// The list of entities that are waiting to be loaded.
|
|
||||||
std::vector<Entity*> entitiesPendingLoading;
|
|
||||||
|
|
||||||
// The list of entities that are waiting to be removed.
|
|
||||||
std::vector<Entity*> entitiesPendingRemoval;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace SE {
|
|
||||||
|
|
||||||
// Forward declaration.
|
|
||||||
class EntityComponent;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A System is a routine that operates over the Components in an Entity.
|
|
||||||
* A System may be local to a given Entity, or it may be glboal to the Scene.
|
|
||||||
*
|
|
||||||
* Components must be registered to the System that works over them.
|
|
||||||
*
|
|
||||||
* TODO: Think of better names for these functions
|
|
||||||
*/
|
|
||||||
class EntitySystem {
|
|
||||||
public:
|
|
||||||
|
|
||||||
virtual ~EntitySystem();
|
|
||||||
virtual std::string& GetName() const = 0;
|
|
||||||
|
|
||||||
// Pre-Component Register
|
|
||||||
virtual void Initialize() {}
|
|
||||||
|
|
||||||
// Post-Component Register
|
|
||||||
virtual void Initialized() {}
|
|
||||||
|
|
||||||
// Pre-Component Unregister
|
|
||||||
virtual void Uninitialize() {}
|
|
||||||
|
|
||||||
// Post-Component Unregister
|
|
||||||
virtual void Uninitialized() {}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
|
|
||||||
// Register a Component with this System
|
|
||||||
virtual void RegisterComponent(EntityComponent* component) = 0;
|
|
||||||
// Unregister a Component from this system
|
|
||||||
virtual void UnregisterComponent(EntityComponent* component) = 0;
|
|
||||||
|
|
||||||
// Update over all Components. TODO: Context?
|
|
||||||
virtual void Update() = 0;
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
#include <id/ID.h>
|
|
||||||
#include <atomic>
|
|
||||||
|
|
||||||
namespace SE {
|
|
||||||
static std::atomic<uint64_t> entityID = 1;
|
|
||||||
EntityID EntityID::Generate() {
|
|
||||||
EntityID id(entityID++);
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::atomic<uint64_t> componentID = 1;
|
|
||||||
ComponentID ComponentID::Generate() {
|
|
||||||
ComponentID id(componentID++);
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
|
||||||
|
|
||||||
# Set up Catch2 testing
|
|
||||||
list(APPEND CMAKE_MODULE_PATH "cmake")
|
|
||||||
enable_testing()
|
|
||||||
|
|
||||||
# Set up asset sourceset
|
|
||||||
FILE(GLOB_RECURSE SOURCES src/*.cpp src/*.h)
|
|
||||||
FILE(GLOB_RECURSE TESTS test/*.cpp)
|
|
||||||
|
|
||||||
add_library(shadow-asset ${SOURCES})
|
|
||||||
|
|
||||||
# Set up test executable
|
|
||||||
add_executable(shadow-asset-test ${TESTS})
|
|
||||||
target_link_libraries(shadow-asset-test PRIVATE Catch2::Catch2 shadow-utils)
|
|
||||||
|
|
||||||
# Enable testing on the executable
|
|
||||||
include(CTest)
|
|
||||||
include(Catch)
|
|
||||||
catch_discover_tests(shadow-asset-test)
|
|
Loading…
Reference in New Issue
Block a user