From 79121cf948b1016da5787481d047301ed7e5201c Mon Sep 17 00:00:00 2001 From: Curle Date: Sun, 7 Apr 2024 16:35:34 +0100 Subject: [PATCH] Add filesystem support from the assets branch --- .../shadow-engine/assets/CMakeLists.txt | 4 +- .../assets/inc/shadow/assets/Mesh.h | 17 - .../assets/inc/shadow/assets/fs/file.h | 114 ++++++ .../assets/inc/shadow/assets/fs/hash.h | 158 +++++++++ .../assets/inc/shadow/assets/fs/iostream.h | 131 +++++++ .../assets/inc/shadow/assets/fs/path.h | 67 ++++ .../assets/inc/shadow/assets/fs/xxhash.h | 182 ++++++++++ .../inc/shadow/assets/management/delegate.h | 92 +++++ .../shadow/assets/management/delegate_list.h | 58 ++++ .../assets/management/synchronization.h | 77 +++++ .../assets/inc/shadow/assets/str/string.h | 12 + .../shadow/shadow-engine/assets/src/Mesh.cpp | 26 -- .../shadow-engine/assets/src/fs/file.cpp | 324 ++++++++++++++++++ .../shadow-engine/assets/src/fs/hash.cpp | 148 ++++++++ .../shadow-engine/assets/src/fs/iostream.cpp | 217 ++++++++++++ .../shadow-engine/assets/src/fs/path.cpp | 121 +++++++ .../assets/src/management/synchronization.cpp | 72 ++++ 17 files changed, 1775 insertions(+), 45 deletions(-) delete mode 100644 projs/shadow/shadow-engine/assets/inc/shadow/assets/Mesh.h create mode 100644 projs/shadow/shadow-engine/assets/inc/shadow/assets/fs/file.h create mode 100644 projs/shadow/shadow-engine/assets/inc/shadow/assets/fs/hash.h create mode 100644 projs/shadow/shadow-engine/assets/inc/shadow/assets/fs/iostream.h create mode 100644 projs/shadow/shadow-engine/assets/inc/shadow/assets/fs/path.h create mode 100644 projs/shadow/shadow-engine/assets/inc/shadow/assets/fs/xxhash.h create mode 100644 projs/shadow/shadow-engine/assets/inc/shadow/assets/management/delegate.h create mode 100644 projs/shadow/shadow-engine/assets/inc/shadow/assets/management/delegate_list.h create mode 100644 projs/shadow/shadow-engine/assets/inc/shadow/assets/management/synchronization.h create mode 100644 projs/shadow/shadow-engine/assets/inc/shadow/assets/str/string.h delete mode 100644 projs/shadow/shadow-engine/assets/src/Mesh.cpp create mode 100644 projs/shadow/shadow-engine/assets/src/fs/file.cpp create mode 100644 projs/shadow/shadow-engine/assets/src/fs/hash.cpp create mode 100644 projs/shadow/shadow-engine/assets/src/fs/iostream.cpp create mode 100644 projs/shadow/shadow-engine/assets/src/fs/path.cpp create mode 100644 projs/shadow/shadow-engine/assets/src/management/synchronization.cpp diff --git a/projs/shadow/shadow-engine/assets/CMakeLists.txt b/projs/shadow/shadow-engine/assets/CMakeLists.txt index 73460bfa..031da61c 100644 --- a/projs/shadow/shadow-engine/assets/CMakeLists.txt +++ b/projs/shadow/shadow-engine/assets/CMakeLists.txt @@ -1,9 +1,9 @@ FILE(GLOB_RECURSE SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/*.cpp -) + ) target_shadow_module(shadow-engine SOURCES ${SOURCES} INCLUDE_DIR ${CMAKE_CURRENT_LIST_DIR}/inc/ -) \ No newline at end of file + ) \ No newline at end of file diff --git a/projs/shadow/shadow-engine/assets/inc/shadow/assets/Mesh.h b/projs/shadow/shadow-engine/assets/inc/shadow/assets/Mesh.h deleted file mode 100644 index 57e84cc7..00000000 --- a/projs/shadow/shadow-engine/assets/inc/shadow/assets/Mesh.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include -#include - -namespace SH::Assets { - //Mesh with verts for a cube - class Mesh { - public: - Mesh(); - - //The vertices of the mesh - std::vector vertices; - //The indices of the mesh - std::vector indices; - }; -} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/assets/inc/shadow/assets/fs/file.h b/projs/shadow/shadow-engine/assets/inc/shadow/assets/fs/file.h new file mode 100644 index 00000000..5a8947a6 --- /dev/null +++ b/projs/shadow/shadow-engine/assets/inc/shadow/assets/fs/file.h @@ -0,0 +1,114 @@ +#pragma once +#include +#include "iostream.h" +#include "path.h" + +template struct Delegate; + +namespace ShadowEngine { + + // An input stream that can read a file on disk. + struct FileInput final : InputStream { + FileInput(); + ~FileInput() = default; + + [[nodiscard]] bool open(const 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(const 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; + // 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 createDiskFS(const std::string& basePath); + // Create a Virtual Filesystem based on the given path. + static std::unique_ptr createVFS(const std::string& basePath); + + virtual ~FileSystem() = default; + + // Open a file for reading. + virtual bool open(const std::string& path, FileInput& input) = 0; + // Open a file for writing. + virtual bool open(const std::string& path, FileOutput& output) = 0; + // Check whether a file exists at the given path. + virtual bool fileExists(const std::string& path) = 0; + // Get the time a file at the given path was last modified. + virtual size_t getLastModified(const std::string& path) = 0; + // Copy a file from one path to another. + virtual bool copyFile(const std::string& from, const std::string& to) = 0; + // Move a file from one path to another. + virtual bool moveFile(const std::string& from, const std::string& to) = 0; + // Disassociate any files at the given path (not an immediate delete) + virtual bool deleteFile(const 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(const 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; + }; +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/assets/inc/shadow/assets/fs/hash.h b/projs/shadow/shadow-engine/assets/inc/shadow/assets/fs/hash.h new file mode 100644 index 00000000..e1abd33c --- /dev/null +++ b/projs/shadow/shadow-engine/assets/inc/shadow/assets/fs/hash.h @@ -0,0 +1,158 @@ +#pragma once +#include + +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(const 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(const 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(const 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(const 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 struct HashFunc; + + template<> struct HashFunc { + static uint32_t get(const HeapHash& h) { + const size_t hash = h.getHash(); + return uint32_t(hash & (hash >> 16)); + } + }; + + template<> struct HashFunc { + static uint32_t get(const StableHash& h) { + const size_t hash = h.getHash(); + return uint32_t(hash & (hash >> 16)); + } + }; + + template<> struct HashFunc { + static uint32_t get(const HeapHash32& h) { + return h.getHash(); + } + }; + + template<> struct HashFunc { + static uint32_t get(const StableHash& h) { + return h.getHash(); + } + }; +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/assets/inc/shadow/assets/fs/iostream.h b/projs/shadow/shadow-engine/assets/inc/shadow/assets/fs/iostream.h new file mode 100644 index 00000000..1c5c7c81 --- /dev/null +++ b/projs/shadow/shadow-engine/assets/inc/shadow/assets/fs/iostream.h @@ -0,0 +1,131 @@ +#pragma once +#include + +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 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 void read(T& val) { read(&val, sizeof(T)); } + template 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 void write(const T& val); + + private: + uint8_t* buffer; + size_t capacity; + size_t usage; + }; + + template void OutputMemoryStream::write(const T& val){ + write(&val, sizeof(T)); + } + + + template <> inline void OutputMemoryStream::write(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 + 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 + T InputStream::read() { + T v; + read(&v, sizeof(T)); + return v; + } + + template<> inline bool InputStream::read() { + uint8_t v; + read(&v, sizeof(bool)); + return v; + } + + template + bool OutputStream::write(const T &val) { + return write(&val, sizeof(T)); + } +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/assets/inc/shadow/assets/fs/path.h b/projs/shadow/shadow-engine/assets/inc/shadow/assets/fs/path.h new file mode 100644 index 00000000..47f9cd6b --- /dev/null +++ b/projs/shadow/shadow-engine/assets/inc/shadow/assets/fs/path.h @@ -0,0 +1,67 @@ +#pragma once + +#include +#include "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(const 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(const std::string& path); + // Get the prelude of the given path. + static std::string getPrelude(const std::string& path); + // Get the domain of the given path. + static std::string getDomain(const std::string& path); + // Get the directory of the given path. + static std::string getDirectory(const std::string& path); + // Get the name of the file of the given path. + static std::string getFilename(const std::string& path); + // Get the file extension of the given path. + static std::string getExtension(const std::string& path); + // Check if the path has the given extension. + static bool hasExtension(const std::string& path, const std::string& ext); + // Replace the extension of the given path. + static std::string replaceExtension(const std::string& path, const 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; + }; +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/assets/inc/shadow/assets/fs/xxhash.h b/projs/shadow/shadow-engine/assets/inc/shadow/assets/fs/xxhash.h new file mode 100644 index 00000000..e75b9c9c --- /dev/null +++ b/projs/shadow/shadow-engine/assets/inc/shadow/assets/fs/xxhash.h @@ -0,0 +1,182 @@ +#pragma once +#include // 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]); + } +}; \ No newline at end of file diff --git a/projs/shadow/shadow-engine/assets/inc/shadow/assets/management/delegate.h b/projs/shadow/shadow-engine/assets/inc/shadow/assets/management/delegate.h new file mode 100644 index 00000000..5ef30c5e --- /dev/null +++ b/projs/shadow/shadow-engine/assets/inc/shadow/assets/management/delegate.h @@ -0,0 +1,92 @@ +#pragma once + +namespace ShadowEngine { + + template 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 struct Delegate { + private: + using InstancePtr = void*; + using InternalFunction = R (*)(InstancePtr, Args...); + struct Stub { + InstancePtr first; + InternalFunction second; + }; + + template static R FunctionStub(InstancePtr, Args... args) { + return (Function)(args...); + } + + template + static R ClassMethodStub(InstancePtr instance, Args... args) { + return (static_cast(instance)->*Function)(args...); + } + + template + static R ClassMethodStub(InstancePtr instance, Args... args) { + return (static_cast(instance)->*Function)(args...); + } + + public: + Delegate() { + m_stub.first = nullptr; + m_stub.second = nullptr; + } + + template + 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 void bind() { + m_stub.first = nullptr; + m_stub.second = &FunctionStub; + } + + template void bind(C* instance) { + m_stub.first = instance; + m_stub.second = &ClassMethodStub; + } + + R invoke(Args... args) const { + return m_stub.second(m_stub.first, args...); + } + + bool operator==(const Delegate& rhs) { + return m_stub.first == rhs.m_stub.first && m_stub.second == rhs.m_stub.second; + } + + private: + Stub m_stub; + }; + + template struct ToDelegate_T; + template struct ToDelegate_T { + using Type = Delegate; + }; + + template using ToDelegate = typename ToDelegate_T::Type; + + template + auto makeDelegate(C* inst) { + ToDelegate res; + res.template bind(inst); + return res; + }; +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/assets/inc/shadow/assets/management/delegate_list.h b/projs/shadow/shadow-engine/assets/inc/shadow/assets/management/delegate_list.h new file mode 100644 index 00000000..42d85b37 --- /dev/null +++ b/projs/shadow/shadow-engine/assets/inc/shadow/assets/management/delegate_list.h @@ -0,0 +1,58 @@ +#pragma once +#include +#include +#include + +namespace ShadowEngine { + + template struct DelegateList; + + template struct DelegateList { + DelegateList() = default; + + template void bind(C* instance) { + Delegate cb; + cb.template bind(instance); + m_delegates.push_back(cb); + } + + template void bind() { + Delegate cb; + cb.template bind(); + m_delegates.push_back(cb); + } + + template void unbind() { + Delegate cb; + cb.template bind(); + for (int i = 0; i < m_delegates.size(); ++i) + { + if (m_delegates[i] == cb) + { + m_delegates.swapAndPop(i); + break; + } + } + } + + template void unbind(C* instance) { + Delegate cb; + cb.template bind(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> m_delegates; + }; +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/assets/inc/shadow/assets/management/synchronization.h b/projs/shadow/shadow-engine/assets/inc/shadow/assets/management/synchronization.h new file mode 100644 index 00000000..364bf4fd --- /dev/null +++ b/projs/shadow/shadow-engine/assets/inc/shadow/assets/management/synchronization.h @@ -0,0 +1,77 @@ +#pragma once + +#include + +#ifdef __linux__ +#include +#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; + }; +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/assets/inc/shadow/assets/str/string.h b/projs/shadow/shadow-engine/assets/inc/shadow/assets/str/string.h new file mode 100644 index 00000000..02749482 --- /dev/null +++ b/projs/shadow/shadow-engine/assets/inc/shadow/assets/str/string.h @@ -0,0 +1,12 @@ +#pragma once +#include + +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); + } +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/assets/src/Mesh.cpp b/projs/shadow/shadow-engine/assets/src/Mesh.cpp deleted file mode 100644 index 2197341e..00000000 --- a/projs/shadow/shadow-engine/assets/src/Mesh.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "shadow/assets/Mesh.h" - -SH::Assets::Mesh::Mesh() { - //Create a cube - //AI generated verts and indices LOL - //Should be something - vertices = { - {-0.5f, -0.5f, -0.5f}, - {0.5f, -0.5f, -0.5f}, - {0.5f, 0.5f, -0.5f}, - {-0.5f, 0.5f, -0.5f}, - {-0.5f, -0.5f, 0.5f}, - {0.5f, -0.5f, 0.5f}, - {0.5f, 0.5f, 0.5f}, - {-0.5f, 0.5f, 0.5f} - }; - - indices = { - 0, 1, 2, 2, 3, 0, - 1, 5, 6, 6, 2, 1, - 7, 6, 5, 5, 4, 7, - 4, 0, 3, 3, 7, 4, - 4, 5, 1, 1, 0, 4, - 3, 2, 6, 6, 7, 3 - }; -} diff --git a/projs/shadow/shadow-engine/assets/src/fs/file.cpp b/projs/shadow/shadow-engine/assets/src/fs/file.cpp new file mode 100644 index 00000000..ccda9a3b --- /dev/null +++ b/projs/shadow/shadow-engine/assets/src/fs/file.cpp @@ -0,0 +1,324 @@ +#include +#include +#include +#include +#include +#include "shadow/assets/fs/iostream.h" +#include "shadow/assets/fs/path.h" +#include "shadow/assets/fs/hash.h" +#include +#include + +namespace ShadowEngine { + + // Because fuck Linux? Need platform-specific source files! +#ifdef _WIN32 + + #define WIN32_LEAN_AND_MEAN +#include + + FileInput::FileInput() { + handle = (void*) INVALID_HANDLE_VALUE; + } + + FileOutput::FileOutput() { + error = false; + handle = (void*) INVALID_HANDLE_VALUE; + } + + bool FileOutput::open(const std::string& path) { + handle = (HANDLE) CreateFile(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + return INVALID_HANDLE_VALUE != handle; + } + + bool FileInput::open(const std::string& path) { + handle = (HANDLE) CreateFile(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + return INVALID_HANDLE_VALUE != handle; + } + + void FileInput::close() { + if (INVALID_HANDLE_VALUE != (HANDLE) handle) { + CloseHandle((HANDLE) handle); + handle = (void*) INVALID_HANDLE_VALUE; + } + } + + void FileOutput::close() { + if (INVALID_HANDLE_VALUE != (HANDLE) handle) { + CloseHandle((HANDLE) handle); + handle = (void*) INVALID_HANDLE_VALUE; + } + } + + size_t FileInput::size() const { + assert(INVALID_HANDLE_VALUE != handle); + return GetFileSize((HANDLE) handle, 0); + } + + size_t FileInput::pos() { + assert(INVALID_HANDLE_VALUE != handle); + return SetFilePointer((HANDLE) handle, 0, nullptr, FILE_CURRENT); + } + + bool FileInput::seek(size_t pos) { + assert(INVALID_HANDLE_VALUE != handle); + LARGE_INTEGER distance; + distance.QuadPart = pos; + return SetFilePointer((HANDLE) handle, distance.u.LowPart, &distance.u.HighPart, FILE_BEGIN) != INVALID_SET_FILE_POINTER; + } + + bool FileInput::read(void* data, size_t size) { + assert(INVALID_HANDLE_VALUE != handle); + DWORD read = 0; + BOOL success = ReadFile((HANDLE) handle, data, (DWORD) size, (LPDWORD) &read, nullptr); + return success && size == read; + } + + void FileOutput::flush() { + assert(handle != nullptr); + FlushFileBuffers((HANDLE) handle); + } + + bool FileOutput::write(const void* data, size_t size) { + assert(handle != INVALID_HANDLE_VALUE); + size_t written = 0; + WriteFile((HANDLE) handle, data, (DWORD) size, (LPDWORD) &written, nullptr); + error = error | size != written; + return !error; + } + +#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(const std::string& path) : sem(0, 0xffff) { + setBasePath(path); + } + + bool hasWork() override { + return workCounter != 0; + } + + std::string const& getBasePath() const override { return basePath; } + void setBasePath(const 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(); + + ShadowEngine::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(const std::string& path, FileInput& file) override { + return file.open(basePath.append(path)); + } + + bool open(const std::string& path, FileOutput& file) override { + return file.open(basePath.append(path)); + } + + bool deleteFile(const std::string& path) override { + return std::remove((basePath.append(path).c_str())); + } + + bool moveFile(const std::string& from, const 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(const std::string& from, const 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(const std::string& path) override { + return std::filesystem::exists(path); + } + + size_t getLastModified(const 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: Run Management + std::string basePath; + std::vector queue; + uint64_t workCounter; + std::vector finished; + Mutex mutex; + Semaphore sem; + + uint32_t lastID; + + }; + + struct VFS : DiskFS { + VFS(const 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(); + for (size_t i = 0; i < count; i++) { + const auto hash = pack.read(); + PackFile& file = packFiles[hash]; + file.offset = pack.read(); + file.size = pack.read(); + } + } + + ~VFS() { pack.close(); } + + bool readSync(const Path& path, OutputMemoryStream& content) override { + std::string basename = Path::getFilename(const_cast(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 packFiles; + FileInput pack; + }; + + std::unique_ptr FileSystem::createDiskFS(const std::string& basePath) { + return std::make_unique(basePath); + } + + std::unique_ptr FileSystem::createVFS(const std::string& basePath) { + return std::make_unique(basePath); + } +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/assets/src/fs/hash.cpp b/projs/shadow/shadow-engine/assets/src/fs/hash.cpp new file mode 100644 index 00000000..2f01069c --- /dev/null +++ b/projs/shadow/shadow-engine/assets/src/fs/hash.cpp @@ -0,0 +1,148 @@ +#include +#include + +namespace ShadowEngine { + + StableHash::StableHash(const void *data, uint32_t length) { + hash = XXHash64::hash(data, length, 0); + } + + StableHash::StableHash(const 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(const 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(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(const 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); + } +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/assets/src/fs/iostream.cpp b/projs/shadow/shadow-engine/assets/src/fs/iostream.cpp new file mode 100644 index 00000000..f02f7bb6 --- /dev/null +++ b/projs/shadow/shadow-engine/assets/src/fs/iostream.cpp @@ -0,0 +1,217 @@ + +#include +#include "shadow/assets/fs/iostream.h" + +namespace ShadowEngine { + + OutputMemoryStream::OutputMemoryStream(void *data, size_t size) + : buffer(static_cast(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(malloc(size)); + memcpy(temp, buffer, capacity); + delete[] buffer; + buffer = temp; + capacity = size; + } + + uint8_t *OutputMemoryStream::release() { + auto* temp = static_cast(malloc(usage)); + memcpy(temp, buffer, usage); + free(); + return temp; + } + + InputMemoryStream::InputMemoryStream(const void *data, size_t size) + : data(static_cast(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 }; + } +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/assets/src/fs/path.cpp b/projs/shadow/shadow-engine/assets/src/fs/path.cpp new file mode 100644 index 00000000..d63ca3d9 --- /dev/null +++ b/projs/shadow/shadow-engine/assets/src/fs/path.cpp @@ -0,0 +1,121 @@ + +#include +#include +#include +#include +#include + +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(const 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(const std::string &path) { + return path.substr(0, path.find_first_of(":")); + } + + std::string Path::getDomain(const std::string &path) { + return path.substr(path.find_first_of(":"), path.find_first_of("/")); + } + + std::string Path::getDirectory(const std::string &path) { + return path.substr(path.find_first_of(":"), path.find_last_of("/")); + } + + std::string Path::getFilename(const std::string &path) { + return path.substr(path.find_last_of("/"), path.find_last_of(".")); + } + + std::string Path::getExtension(const std::string &path) { + return path.substr(path.find_last_of("."), path.length()); + } + + std::string Path::replaceExtension(const std::string &path, const std::string &newExt) { + return path.substr(0, path.length() - newExt.length()).append(newExt); + } + + bool Path::hasExtension(const std::string &path, const std::string &ext) { + return path.find_last_of(ext) == path.length() - ext.length(); + } + + PathInfo::PathInfo(const 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()); + } + +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/assets/src/management/synchronization.cpp b/projs/shadow/shadow-engine/assets/src/management/synchronization.cpp new file mode 100644 index 00000000..910db104 --- /dev/null +++ b/projs/shadow/shadow-engine/assets/src/management/synchronization.cpp @@ -0,0 +1,72 @@ + +// This doesn't work on Linux. Sucks to be you? dpeter won't let me do system-specific source files. +#ifdef _WIN32 + +#include +#define WIN32_LEAN_AND_MEAN +#include +#include "shadow/assets/management/synchronization.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 \ No newline at end of file