diff --git a/DqnUnitTest.cpp b/DqnUnitTest.cpp index de311b5..47b8c1a 100644 --- a/DqnUnitTest.cpp +++ b/DqnUnitTest.cpp @@ -1844,6 +1844,15 @@ void DqnFile_Test() } +void PlatformSleep(int milliseconds) +{ +#if defined(DQN__IS_UNIX) + usleep(milliseconds * 1000); +#else + Sleep(milliseconds); +#endif +} + void DqnTimer_Test() { LOG_HEADER(); @@ -1851,18 +1860,13 @@ void DqnTimer_Test() if (1) { + int sleepTimeInMs = 250; f64 startInMs = DqnTimer_NowInMs(); -#if defined(DQN__IS_UNIX) - u32 sleepTimeInMs = 1; - sleep(sleepTimeInMs); - Log("start: %f, end: %f", startInMs, endInMs); - -#else - u32 sleepTimeInMs = 1000; - Sleep(sleepTimeInMs); -#endif + PlatformSleep(sleepTimeInMs); f64 endInMs = DqnTimer_NowInMs(); + DQN_ASSERT((startInMs + sleepTimeInMs) <= endInMs); + Log("start: %f, end: %f", startInMs, endInMs); Log(Status::Ok, "Timer advanced in time over 1 second"); globalIndent++; @@ -2342,44 +2346,52 @@ void DqnCatalog_Test() { LOG_HEADER(); - DqnCatalog textCatalog = {}; - textCatalog.PollAssets(); + DqnCatalog catalog = {}; + catalog.PollAssets(); - char const bufA[] = "aaaa"; - char const bufX[] = "xxxx"; + DqnCatalogPath path = "DqnCatalog_TrackFile"; + DqnFile_Delete(path.str); - DqnFixedString128 testFile = "DqnCatalog_TrackFile"; - - DqnFile file = {}; - - // Write file A and check we are able to open it up in the catalog + // Initially write the file and check the catalog is able to open it up { - DQN_ASSERTM( - file.Open(testFile.str, DqnFile::Flag::FileReadWrite, DqnFile::Action::ForceCreate), - "Could not create testing file for DqnCatalog"); - file.Write(reinterpret_cast(bufA), DQN_CHAR_COUNT(bufA), 0); - file.Close(); - - RawBuf *buf = textCatalog.GetIfUpdated(testFile); - DQN_ASSERT(DqnMem_Cmp(buf->buffer, bufA, DQN_CHAR_COUNT(bufA)) == 0); + char const writeBuf[] = "aaaa"; + DqnFile_WriteAll(path.str, reinterpret_cast(writeBuf), DQN_CHAR_COUNT(writeBuf)); + RawBuf *buf = catalog.GetIfUpdated(path); + DQN_ASSERT(DqnMem_Cmp(buf->buffer, writeBuf, DQN_CHAR_COUNT(writeBuf)) == 0); Log(Status::Ok, "Catalog finds and loads on demand new file"); } - // Write file B check that it has been updated + // Update the file and check that the GetIfUpdated returns a non-nullptr (because the entry is updated) { - file = {}; - DQN_ASSERTM( - file.Open(testFile.str, DqnFile::Flag::FileReadWrite, DqnFile::Action::ForceCreate), - "Could not create testing file for DqnCatalog"); - file.Write(reinterpret_cast(bufX), DQN_CHAR_COUNT(bufX), 0); - file.Close(); - - RawBuf *buf = textCatalog.GetIfUpdated(testFile); - DQN_ASSERT(DqnMem_Cmp(buf->buffer, bufX, DQN_CHAR_COUNT(bufX)) == 0); + PlatformSleep(1000); + char const writeBuf[] = "xxxx"; + DqnFile_WriteAll(path.str, reinterpret_cast(writeBuf), DQN_CHAR_COUNT(writeBuf)); + RawBuf *buf = catalog.GetIfUpdated(path); + DQN_ASSERT(DqnMem_Cmp(buf->buffer, writeBuf, DQN_CHAR_COUNT(writeBuf)) == 0); Log(Status::Ok, "Catalog finds updated file after subsequent write"); } - DqnFile_Delete(testFile.str); + // Update the file and get the catalog to poll the entries and check it has been updated + { + PlatformSleep(1000); + char const writeBuf[] = "abcd"; + DqnFile_WriteAll(path.str, reinterpret_cast(writeBuf), DQN_CHAR_COUNT(writeBuf)); + catalog.PollAssets(); + + RawBuf *buf = catalog.GetIfUpdated(path); + DQN_ASSERT(DqnMem_Cmp(buf->buffer, writeBuf, DQN_CHAR_COUNT(writeBuf)) == 0); + Log(Status::Ok, "Catalog finds updated file using the poll asset interface"); + } + + // Update the file and get the catalog to poll the entries and check it has been updated + { + catalog.Erase(path.str); + RawBuf *buf = catalog.Get(path); + DQN_ASSERT(buf == nullptr); + Log(Status::Ok, "Catalog erase removes file from catalog"); + } + + DqnFile_Delete(path.str); } int main(void) diff --git a/dqn.h b/dqn.h index 40db474..70db6da 100644 --- a/dqn.h +++ b/dqn.h @@ -2600,21 +2600,37 @@ template void DqnVArray::EraseStable(isize index) template using DqnVHashTableHashingProc = isize(*)(isize count, Key const &data); template using DqnVHashTableEqualsProc = bool (*)(Key const &a, Key const &b); -#define DQN_VHASH_TABLE_HASHING_PROC(name) template inline isize name(isize count, Key const &key) -DQN_VHASH_TABLE_HASHING_PROC(DqnVHashTableDefaultHash) -{ - const u64 SEED = 0x9747B28CAB3F8A7B; - u64 index64 = DqnHash_Murmur64Seed(&key, sizeof(key), SEED); - isize result = index64 % count; - return result; -} +const u64 DQN_VHASH_TABLE_DEFAULT_SEED = 0x9747B28CAB3F8A7B; +#define DQN_VHASH_TABLE_HASHING_PROC(name, Type) inline isize name(isize count, Type const &key) +#define DQN_VHASH_TABLE_EQUALS_PROC(name, Type) inline bool name(Type const &a, Type const &b) + +template DQN_VHASH_TABLE_HASHING_PROC(DqnVHashTableDefaultHash, T) { return DqnHash_Murmur64Seed(&key, sizeof(key), DQN_VHASH_TABLE_DEFAULT_SEED) % count; } +template DQN_VHASH_TABLE_EQUALS_PROC (DqnVHashTableDefaultEquals, T) { return (DqnMem_Cmp(&a, &b, sizeof(a)) == 0); } + +// TODO(doyle): Fix this so we don't have to manually declare the fixed string sizes for hashing and equals +#define DQN_VHASH_TABLE_DEFAULT_FIXED_STRING_PROCS(StringCapacity) \ + template <> \ + DQN_VHASH_TABLE_HASHING_PROC(DqnVHashTableDefaultHash>, \ + DqnFixedString) \ + { \ + return DqnHash_Murmur64Seed(key.str, key.len, DQN_VHASH_TABLE_DEFAULT_SEED) % count; \ + } \ + template <> \ + DQN_VHASH_TABLE_EQUALS_PROC(DqnVHashTableDefaultEquals>, \ + DqnFixedString) \ + { \ + return (a.len == b.len) && (DqnStr_Cmp(a.str, b.str, a.len, Dqn::IgnoreCase::No) == 0); \ + } + +DQN_VHASH_TABLE_DEFAULT_FIXED_STRING_PROCS(1024) +DQN_VHASH_TABLE_DEFAULT_FIXED_STRING_PROCS(512) +DQN_VHASH_TABLE_DEFAULT_FIXED_STRING_PROCS(256) +DQN_VHASH_TABLE_DEFAULT_FIXED_STRING_PROCS(128) +DQN_VHASH_TABLE_DEFAULT_FIXED_STRING_PROCS(64) +DQN_VHASH_TABLE_DEFAULT_FIXED_STRING_PROCS(32) +DQN_VHASH_TABLE_DEFAULT_FIXED_STRING_PROCS(16) +DQN_VHASH_TABLE_DEFAULT_FIXED_STRING_PROCS(8) -#define DQN_VHASH_TABLE_EQUALS_PROC(name) template inline bool name(Key const &a, Key const &b) -DQN_VHASH_TABLE_EQUALS_PROC(DqnVHashTableDefaultEquals) -{ - bool result = (DqnMem_Cmp(&a, &b, sizeof(a)) == 0); - return result; -} #define DQN_VHASH_TABLE_TEMPLATE \ template #DqnCatalog API // ================================================================================================= -template using DqnCatalogLoadProc = bool (*)(DqnFixedString128 const &file, T *data); -#define DQN_CATALOG_LOAD_PROC(name, type) bool name(DqnFixedString128 const &file, type *data) +using DqnCatalogPath = DqnFixedString1024; +template using DqnCatalogLoadProc = bool (*)(DqnCatalogPath const &file, T *data); +#define DQN_CATALOG_LOAD_PROC(name, type) bool name(DqnCatalogPath const &file, type *data) #define DQN_CATALOG_TEMPLATE template LoadAsset> #define DQN_CATALOG_DECL DqnCatalog +#if 0 +struct RawBuf { char *buffer; int len; }; +DQN_CATALOG_LOAD_PROC(CatalogRawLoad, RawBuf) +{ + size_t bufSize; + uint8_t *buf = DqnFile_ReadAll(file.str, &bufSize); + if (!buf) return false; + data->buffer = reinterpret_cast(buf); + data->len = static_cast(bufSize); + return true; +} + +int main(int, char) +{ + DqnCatalog catalog = {}; + RawBuf *file = catalog.GetIfUpdated("path/to/file/"); + if (file) { (void)file; // do work on file } + else { // file not updated since last query } + + while (true) // Or event loop, poll the assets in the catalog + { + catalog.PollAssets(); + } + catalog.Free(); +} +#endif + DQN_CATALOG_TEMPLATE struct DqnCatalog { struct Entry @@ -2949,81 +2993,79 @@ DQN_CATALOG_TEMPLATE struct DqnCatalog bool updated; }; - DqnVHashTable assetTable; + DqnVHashTable assetTable; - // Register the asset and load it if it hasn't yet. - Entry *GetEntry (DqnFixedString128 const &file); // return: Entry, or nullptr if the asset could not be loaded - T *Get (DqnFixedString128 const &file) { Entry *entry = GetEntry(file.str); return (entry) ? &entry->data : nullptr; } - T *GetIfUpdated(DqnFixedString128 const &file); // return: Asset if an update has been detected and not consumed yet. Update is consumed after called. + // Adds the file to the catalog if it has not been added yet. + // return: Asset if an update has been detected and not consumed yet otherwise nullptr. Update is consumed after called. + T *GetIfUpdated(DqnCatalogPath const &file); + Entry *GetEntry (DqnCatalogPath const &file) { Entry *entry = assetTable.Get(file); return entry; } + T *Get (DqnCatalogPath const &file) { Entry *entry = assetTable.Get(file); return (entry) ? &entry->data : nullptr; } + void Erase (DqnCatalogPath const &file) { assetTable.Erase(file); }; - // Call to iterate all loaded assets for updates. - void PollAssets (); + // return: Iterate all loaded assets for updates, true if atleast 1 asset was updated. + bool PollAssets (); void Free () { assetTable.Free(); } + + // NOTE: Unlikely you will need to use. Prefer GetIfUpdated. + // Manually invoke an update on the entry by querying its last write time on disk and updating accordingly. + bool QueryAndUpdateAsset(DqnCatalogPath const &file, Entry *entry); }; -DQN_CATALOG_TEMPLATE typename DQN_CATALOG_DECL::Entry * -DQN_CATALOG_DECL::GetEntry(DqnFixedString128 const &file) +DQN_CATALOG_TEMPLATE bool DQN_CATALOG_DECL::QueryAndUpdateAsset(DqnCatalogPath const &file, Entry *entry) { - bool existed = false; - Entry *result = this->assetTable.GetOrMake(file, &existed); - - if (!existed) + DqnFileInfo info = {}; + if (!DqnFile_GetInfo(file.str, &info)) { - T newData = {}; - DqnFileInfo info = {}; - if (DqnFile_GetInfo(file.str, &info) && LoadAsset(file, &newData)) - { - result->lastWriteTimeInS = info.lastWriteTimeInS; - result->data = newData; - result->updated = true; - } - else - { - result = nullptr; - DQN_LOGE("Catalog could not load file: %s\n", file.str); - } + DQN_LOGE("Catalog could not get file info for: %s\n", file.str); + return false; } - return result; + if (entry->lastWriteTimeInS == info.lastWriteTimeInS) + return true; + + T newData = {}; + if (LoadAsset(file, &newData)) + { + entry->lastWriteTimeInS = info.lastWriteTimeInS; + entry->data = newData; + entry->updated = true; + } + else + { + DQN_LOGE("Catalog could not load file: %s\n", file.str); + return false; + } + + return true; } -DQN_CATALOG_TEMPLATE T *DQN_CATALOG_DECL::GetIfUpdated(DqnFixedString128 const &file) +DQN_CATALOG_TEMPLATE T *DQN_CATALOG_DECL::GetIfUpdated(DqnCatalogPath const &file) { - Entry *entry = this->GetEntry(file.str); - if (entry) + Entry *entry = this->assetTable.GetOrMake(file); + if (QueryAndUpdateAsset(file, entry)) { if (entry->updated) entry->updated = false; else entry = nullptr; } + else + { + entry = nullptr; + } - return (entry) ? &entry->data : nullptr; + return &entry->data; } -DQN_CATALOG_TEMPLATE void DQN_CATALOG_DECL::PollAssets() +DQN_CATALOG_TEMPLATE bool DQN_CATALOG_DECL::PollAssets() { + bool result = false; for (auto it = this->assetTable.Begin(); it != this->assetTable.End(); ++it) { - DqnFixedString128 const *file = &it.entry->key; - Entry *entry = &it.entry->item; - - DqnFileInfo info = {}; - if (!DqnFile_GetInfo(file->str, &info)) - continue; - - if (entry->lastWriteTimeInS == info.lastWriteTimeInS) - continue; - - T newData = {}; - if (LoadAsset(*file, &newData)) - { - entry->lastWriteTimeInS = info.lastWriteTimeInS; - entry->data = newData; - } - else - { - DQN_LOGE("Catalog could not load file: %s\n", file->str); - } + DqnCatalogPath const *file = &it.entry->key; + Entry *entry = &it.entry->item; + result |= QueryAndUpdateAsset(*file, entry); } + + return result; }