prog
This commit is contained in:
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"components": {
|
||||||
|
"position": {
|
||||||
|
"position": [0.0, 0.0, 0.0],
|
||||||
|
"rotation": [0.0, 0.0, 0.0],
|
||||||
|
"scale": [1.0, 1.0, 1.0]
|
||||||
|
},
|
||||||
|
"renderable": {
|
||||||
|
"type": "shader_material",
|
||||||
|
"priority": 0,
|
||||||
|
"shaderType": 1,
|
||||||
|
"stateFlags": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"components": {
|
||||||
|
"position": {
|
||||||
|
"position": [0.0, 0.0, 0.0],
|
||||||
|
"rotation": [0.0, 0.0, 0.0],
|
||||||
|
"scale": [1.0, 1.0, 1.0]
|
||||||
|
},
|
||||||
|
"camera": {
|
||||||
|
"projType": "perspective",
|
||||||
|
"nearClip": 0.1,
|
||||||
|
"farClip": 5000.0,
|
||||||
|
"fov": 45.0
|
||||||
|
},
|
||||||
|
"renderable": {
|
||||||
|
"type": "shader_material",
|
||||||
|
"priority": 0,
|
||||||
|
"shaderType": 1,
|
||||||
|
"stateFlags": 2
|
||||||
|
},
|
||||||
|
"physics": {
|
||||||
|
"bodyType": "dynamic",
|
||||||
|
"shapeType": "cube",
|
||||||
|
"halfExtents": [0.5, 0.5, 0.5],
|
||||||
|
"velocity": [0.0, 0.0, 0.0],
|
||||||
|
"gravityScale": 1.0
|
||||||
|
},
|
||||||
|
"trigger": {
|
||||||
|
"min": [-0.5, -0.5, -0.5],
|
||||||
|
"max": [0.5, 0.5, 0.5]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
# Find link platform-specific libraries
|
# Find link platform-specific libraries
|
||||||
find_package(SDL2 REQUIRED)
|
find_package(SDL2 REQUIRED)
|
||||||
find_package(OpenGL REQUIRED)
|
find_package(OpenGL REQUIRED)
|
||||||
|
find_package(LIBZIP REQUIRED)
|
||||||
# find_package(CURL REQUIRED)
|
# find_package(CURL REQUIRED)
|
||||||
|
|
||||||
# Setup endianess at compile time to optimize.
|
# Setup endianess at compile time to optimize.
|
||||||
@@ -23,6 +24,7 @@ target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
|||||||
OpenGL::GL
|
OpenGL::GL
|
||||||
GL
|
GL
|
||||||
m
|
m
|
||||||
|
libzip::zip
|
||||||
# CURL::libcurl
|
# CURL::libcurl
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -56,4 +56,6 @@ add_subdirectory(network)
|
|||||||
add_subdirectory(overworld)
|
add_subdirectory(overworld)
|
||||||
add_subdirectory(save)
|
add_subdirectory(save)
|
||||||
add_subdirectory(util)
|
add_subdirectory(util)
|
||||||
add_subdirectory(thread)
|
add_subdirectory(thread)
|
||||||
|
add_subdirectory(asset)
|
||||||
|
add_subdirectory(scene)
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Copyright (c) 2026 Dominic Masters
|
||||||
|
#
|
||||||
|
# This software is released under the MIT License.
|
||||||
|
# https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||||
|
PUBLIC
|
||||||
|
asset.c
|
||||||
|
)
|
||||||
|
|
||||||
|
add_subdirectory(loader)
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "asset.h"
|
||||||
|
#include "util/memory.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
|
||||||
|
assetmanager_t ASSET_MANAGER;
|
||||||
|
|
||||||
|
errorret_t assetInit(void) {
|
||||||
|
assertIsMainThread("assetInit must be on main thread");
|
||||||
|
memoryZero(&ASSET_MANAGER, sizeof(assetmanager_t));
|
||||||
|
|
||||||
|
int_t zipErr = 0;
|
||||||
|
ASSET_MANAGER.archive = zip_open(
|
||||||
|
ASSET_FILE_NAME, ZIP_RDONLY, &zipErr
|
||||||
|
);
|
||||||
|
if(!ASSET_MANAGER.archive) {
|
||||||
|
errorThrow(
|
||||||
|
"Failed to open %s: error %d", ASSET_FILE_NAME, zipErr
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
threadMutexInit(&ASSET_MANAGER.mutex);
|
||||||
|
threadInit(&ASSET_MANAGER.loaderThread, assetLoaderThread);
|
||||||
|
threadStart(&ASSET_MANAGER.loaderThread);
|
||||||
|
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
void assetUpdate(void) {
|
||||||
|
assertIsMainThread("assetUpdate must be on main thread");
|
||||||
|
threadMutexLock(&ASSET_MANAGER.mutex);
|
||||||
|
for(uint8_t i = 0; i < ASSET_ENTRY_COUNT_MAX; i++) {
|
||||||
|
assetentry_t *e = &ASSET_MANAGER.entries[i];
|
||||||
|
if(e->state != ASSET_ENTRY_STATE_SYNC_PENDING) continue;
|
||||||
|
if(ASSET_LOADER_CALLBACKS[e->type].loadSync) {
|
||||||
|
threadMutexUnlock(&ASSET_MANAGER.mutex);
|
||||||
|
ASSET_LOADER_CALLBACKS[e->type].loadSync(e);
|
||||||
|
threadMutexLock(&ASSET_MANAGER.mutex);
|
||||||
|
}
|
||||||
|
if(e->state == ASSET_ENTRY_STATE_SYNC_PENDING) {
|
||||||
|
e->state = ASSET_ENTRY_STATE_LOADED;
|
||||||
|
}
|
||||||
|
threadMutexSignal(&ASSET_MANAGER.mutex);
|
||||||
|
}
|
||||||
|
threadMutexUnlock(&ASSET_MANAGER.mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t assetDispose(void) {
|
||||||
|
assertIsMainThread("assetDispose must be on main thread");
|
||||||
|
|
||||||
|
threadStopRequest(&ASSET_MANAGER.loaderThread);
|
||||||
|
threadMutexLock(&ASSET_MANAGER.mutex);
|
||||||
|
threadMutexSignal(&ASSET_MANAGER.mutex);
|
||||||
|
threadMutexUnlock(&ASSET_MANAGER.mutex);
|
||||||
|
threadStop(&ASSET_MANAGER.loaderThread);
|
||||||
|
|
||||||
|
for(uint8_t i = 0; i < ASSET_ENTRY_COUNT_MAX; i++) {
|
||||||
|
assetentry_t *e = &ASSET_MANAGER.entries[i];
|
||||||
|
if(e->state == ASSET_ENTRY_STATE_IDLE) continue;
|
||||||
|
if(!ASSET_LOADER_CALLBACKS[e->type].dispose) continue;
|
||||||
|
ASSET_LOADER_CALLBACKS[e->type].dispose(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ASSET_MANAGER.archive) {
|
||||||
|
zip_close(ASSET_MANAGER.archive);
|
||||||
|
ASSET_MANAGER.archive = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
threadMutexDispose(&ASSET_MANAGER.mutex);
|
||||||
|
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
assetentry_t *assetGetEntry(
|
||||||
|
const char_t *path,
|
||||||
|
const assetloadertype_t type,
|
||||||
|
const assetloaderinput_t *input
|
||||||
|
) {
|
||||||
|
assertNotNull(path, "Path cannot be null");
|
||||||
|
|
||||||
|
threadMutexLock(&ASSET_MANAGER.mutex);
|
||||||
|
|
||||||
|
for(uint8_t i = 0; i < ASSET_ENTRY_COUNT_MAX; i++) {
|
||||||
|
assetentry_t *e = &ASSET_MANAGER.entries[i];
|
||||||
|
if(e->state == ASSET_ENTRY_STATE_IDLE) continue;
|
||||||
|
if(e->type != type) continue;
|
||||||
|
if(strncmp(e->path, path, ASSET_PATH_MAX) != 0) continue;
|
||||||
|
threadMutexUnlock(&ASSET_MANAGER.mutex);
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
assetentry_t *entry = NULL;
|
||||||
|
for(uint8_t i = 0; i < ASSET_ENTRY_COUNT_MAX; i++) {
|
||||||
|
if(ASSET_MANAGER.entries[i].state != ASSET_ENTRY_STATE_IDLE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
entry = &ASSET_MANAGER.entries[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!entry) {
|
||||||
|
threadMutexUnlock(&ASSET_MANAGER.mutex);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
memoryZero(entry, sizeof(assetentry_t));
|
||||||
|
strncpy(entry->path, path, ASSET_PATH_MAX - 1);
|
||||||
|
entry->path[ASSET_PATH_MAX - 1] = '\0';
|
||||||
|
entry->type = type;
|
||||||
|
if(input) entry->input = *input;
|
||||||
|
entry->state = ASSET_ENTRY_STATE_QUEUED;
|
||||||
|
|
||||||
|
threadMutexSignal(&ASSET_MANAGER.mutex);
|
||||||
|
threadMutexUnlock(&ASSET_MANAGER.mutex);
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t assetRequireLoaded(assetentry_t *entry) {
|
||||||
|
assertIsMainThread("assetRequireLoaded must be on main thread");
|
||||||
|
assertNotNull(entry, "Entry cannot be null");
|
||||||
|
|
||||||
|
threadMutexLock(&ASSET_MANAGER.mutex);
|
||||||
|
while(
|
||||||
|
entry->state != ASSET_ENTRY_STATE_LOADED &&
|
||||||
|
entry->state != ASSET_ENTRY_STATE_ERROR
|
||||||
|
) {
|
||||||
|
if(entry->state == ASSET_ENTRY_STATE_SYNC_PENDING) {
|
||||||
|
if(ASSET_LOADER_CALLBACKS[entry->type].loadSync) {
|
||||||
|
threadMutexUnlock(&ASSET_MANAGER.mutex);
|
||||||
|
ASSET_LOADER_CALLBACKS[entry->type].loadSync(entry);
|
||||||
|
threadMutexLock(&ASSET_MANAGER.mutex);
|
||||||
|
}
|
||||||
|
if(entry->state == ASSET_ENTRY_STATE_SYNC_PENDING) {
|
||||||
|
entry->state = ASSET_ENTRY_STATE_LOADED;
|
||||||
|
}
|
||||||
|
threadMutexSignal(&ASSET_MANAGER.mutex);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
threadMutexWaitLock(&ASSET_MANAGER.mutex);
|
||||||
|
}
|
||||||
|
bool_t ok = (entry->state == ASSET_ENTRY_STATE_LOADED);
|
||||||
|
threadMutexUnlock(&ASSET_MANAGER.mutex);
|
||||||
|
|
||||||
|
if(!ok) errorThrow("Asset failed to load: %s", entry->path);
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
void assetLock(assetentry_t *entry) {
|
||||||
|
assertNotNull(entry, "Entry cannot be null");
|
||||||
|
threadMutexLock(&ASSET_MANAGER.mutex);
|
||||||
|
entry->refCount++;
|
||||||
|
threadMutexUnlock(&ASSET_MANAGER.mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void assetUnlock(assetentry_t *entry) {
|
||||||
|
assertNotNull(entry, "Entry cannot be null");
|
||||||
|
threadMutexLock(&ASSET_MANAGER.mutex);
|
||||||
|
if(entry->refCount > 0) entry->refCount--;
|
||||||
|
threadMutexUnlock(&ASSET_MANAGER.mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void assetLoaderThread(thread_t *thread) {
|
||||||
|
threadMutexLock(&ASSET_MANAGER.mutex);
|
||||||
|
while(!threadShouldStop(thread)) {
|
||||||
|
assetentry_t *entry = NULL;
|
||||||
|
for(uint8_t i = 0; i < ASSET_ENTRY_COUNT_MAX; i++) {
|
||||||
|
assetentry_t *e = &ASSET_MANAGER.entries[i];
|
||||||
|
if(e->state != ASSET_ENTRY_STATE_QUEUED) continue;
|
||||||
|
entry = e;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!entry) {
|
||||||
|
threadMutexWaitLock(&ASSET_MANAGER.mutex);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry->state = ASSET_ENTRY_STATE_READING;
|
||||||
|
threadMutexUnlock(&ASSET_MANAGER.mutex);
|
||||||
|
|
||||||
|
if(ASSET_LOADER_CALLBACKS[entry->type].loadAsync) {
|
||||||
|
ASSET_LOADER_CALLBACKS[entry->type].loadAsync(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
threadMutexLock(&ASSET_MANAGER.mutex);
|
||||||
|
if(entry->state == ASSET_ENTRY_STATE_READING) {
|
||||||
|
if(ASSET_LOADER_CALLBACKS[entry->type].loadSync) {
|
||||||
|
entry->state = ASSET_ENTRY_STATE_SYNC_PENDING;
|
||||||
|
} else {
|
||||||
|
entry->state = ASSET_ENTRY_STATE_LOADED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
threadMutexSignal(&ASSET_MANAGER.mutex);
|
||||||
|
}
|
||||||
|
threadMutexUnlock(&ASSET_MANAGER.mutex);
|
||||||
|
}
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "dusk.h"
|
||||||
|
#include "error/error.h"
|
||||||
|
#include "thread/thread.h"
|
||||||
|
#include "thread/threadmutex.h"
|
||||||
|
#include <zip.h>
|
||||||
|
#include <yyjson.h>
|
||||||
|
|
||||||
|
#define ASSET_FILE_NAME "dusk.dsk"
|
||||||
|
#define ASSET_PATH_MAX 128
|
||||||
|
#define ASSET_ENTRY_COUNT_MAX 128
|
||||||
|
#define ASSET_JSON_MAX_SIZE (256 * 1024)
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ASSET_LOADER_TYPE_NULL = 0,
|
||||||
|
ASSET_LOADER_TYPE_JSON,
|
||||||
|
ASSET_LOADER_TYPE_COUNT
|
||||||
|
} assetloadertype_t;
|
||||||
|
|
||||||
|
typedef union {
|
||||||
|
uint8_t _pad;
|
||||||
|
} assetloaderinput_t;
|
||||||
|
|
||||||
|
typedef union {
|
||||||
|
struct {
|
||||||
|
uint8_t *bytes;
|
||||||
|
size_t size;
|
||||||
|
} raw;
|
||||||
|
} assetloaderloading_t;
|
||||||
|
|
||||||
|
typedef union {
|
||||||
|
struct {
|
||||||
|
yyjson_doc *doc;
|
||||||
|
yyjson_val *root;
|
||||||
|
} json;
|
||||||
|
} assetloaderdata_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ASSET_ENTRY_STATE_IDLE = 0,
|
||||||
|
ASSET_ENTRY_STATE_QUEUED,
|
||||||
|
ASSET_ENTRY_STATE_READING,
|
||||||
|
ASSET_ENTRY_STATE_SYNC_PENDING,
|
||||||
|
ASSET_ENTRY_STATE_LOADED,
|
||||||
|
ASSET_ENTRY_STATE_ERROR,
|
||||||
|
} assetentrystate_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
assetentrystate_t state;
|
||||||
|
char_t path[ASSET_PATH_MAX];
|
||||||
|
assetloadertype_t type;
|
||||||
|
assetloaderinput_t input;
|
||||||
|
assetloaderloading_t loading;
|
||||||
|
assetloaderdata_t data;
|
||||||
|
uint8_t refCount;
|
||||||
|
} assetentry_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
void (*loadAsync)(assetentry_t *entry);
|
||||||
|
void (*loadSync)(assetentry_t *entry);
|
||||||
|
void (*dispose)(assetentry_t *entry);
|
||||||
|
} assetloadercallbacks_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
assetentry_t entries[ASSET_ENTRY_COUNT_MAX];
|
||||||
|
zip_t *archive;
|
||||||
|
thread_t loaderThread;
|
||||||
|
threadmutex_t mutex;
|
||||||
|
} assetmanager_t;
|
||||||
|
|
||||||
|
extern assetmanager_t ASSET_MANAGER;
|
||||||
|
extern const assetloadercallbacks_t ASSET_LOADER_CALLBACKS[
|
||||||
|
ASSET_LOADER_TYPE_COUNT
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set entry->state to ERROR and return from a loader callback.
|
||||||
|
* Requires the loader callback parameter to be named `entry`.
|
||||||
|
*/
|
||||||
|
#define assetLoaderErrorThrow(msg, ...) \
|
||||||
|
do { \
|
||||||
|
entry->state = ASSET_ENTRY_STATE_ERROR; \
|
||||||
|
return; \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If retval is an error, set entry->state to ERROR and return.
|
||||||
|
* Requires the loader callback parameter to be named `entry`.
|
||||||
|
*/
|
||||||
|
#define assetLoaderErrorChain(retval) \
|
||||||
|
do { \
|
||||||
|
errorret_t _alerr = (retval); \
|
||||||
|
if(errorIsNotOk(_alerr)) { \
|
||||||
|
entry->state = ASSET_ENTRY_STATE_ERROR; \
|
||||||
|
errorCatch(_alerr); \
|
||||||
|
return; \
|
||||||
|
} \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the asset manager and opens the asset archive.
|
||||||
|
* @returns errorret_t
|
||||||
|
*/
|
||||||
|
errorret_t assetInit(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes sync-pending entries. Call once per frame from the
|
||||||
|
* main thread.
|
||||||
|
*/
|
||||||
|
void assetUpdate(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the loader thread, disposes all entries, and closes the
|
||||||
|
* archive.
|
||||||
|
* @returns errorret_t
|
||||||
|
*/
|
||||||
|
errorret_t assetDispose(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets or creates a cached entry for the given path and type.
|
||||||
|
* Signals the loader thread to begin loading.
|
||||||
|
* @param path Asset path within the archive.
|
||||||
|
* @param type Loader type.
|
||||||
|
* @param input Optional per-type config, may be NULL.
|
||||||
|
* @returns Pointer to the entry, or NULL if cache is full.
|
||||||
|
*/
|
||||||
|
assetentry_t *assetGetEntry(
|
||||||
|
const char_t *path,
|
||||||
|
const assetloadertype_t type,
|
||||||
|
const assetloaderinput_t *input
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blocks until entry is LOADED or ERROR. Runs the sync phase
|
||||||
|
* inline when SYNC_PENDING. Must be called from the main thread.
|
||||||
|
* @param entry The entry to wait for.
|
||||||
|
* @returns errorret_t
|
||||||
|
*/
|
||||||
|
errorret_t assetRequireLoaded(assetentry_t *entry);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increments the reference count on an entry.
|
||||||
|
* @param entry The entry to lock.
|
||||||
|
*/
|
||||||
|
void assetLock(assetentry_t *entry);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrements the reference count on an entry.
|
||||||
|
* @param entry The entry to unlock.
|
||||||
|
*/
|
||||||
|
void assetUnlock(assetentry_t *entry);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Background loader thread callback.
|
||||||
|
* @param thread The running thread.
|
||||||
|
*/
|
||||||
|
void assetLoaderThread(thread_t *thread);
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Copyright (c) 2026 Dominic Masters
|
||||||
|
#
|
||||||
|
# This software is released under the MIT License.
|
||||||
|
# https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||||
|
PUBLIC
|
||||||
|
assetloader.c
|
||||||
|
)
|
||||||
|
|
||||||
|
add_subdirectory(json)
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "asset/asset.h"
|
||||||
|
#include "asset/loader/json/assetjsonloader.h"
|
||||||
|
|
||||||
|
const assetloadercallbacks_t ASSET_LOADER_CALLBACKS[
|
||||||
|
ASSET_LOADER_TYPE_COUNT
|
||||||
|
] = {
|
||||||
|
[ASSET_LOADER_TYPE_JSON] = {
|
||||||
|
.loadAsync = assetJsonLoaderAsync,
|
||||||
|
.loadSync = NULL,
|
||||||
|
.dispose = assetJsonLoaderDispose
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2026 Dominic Masters
|
||||||
|
#
|
||||||
|
# This software is released under the MIT License.
|
||||||
|
# https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||||
|
PUBLIC
|
||||||
|
assetjsonloader.c
|
||||||
|
)
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "assetjsonloader.h"
|
||||||
|
#include "util/memory.h"
|
||||||
|
|
||||||
|
void assetJsonLoaderAsync(assetentry_t *entry) {
|
||||||
|
zip_int64_t idx = zip_name_locate(
|
||||||
|
ASSET_MANAGER.archive, entry->path, 0
|
||||||
|
);
|
||||||
|
if(idx < 0) {
|
||||||
|
assetLoaderErrorThrow("JSON not found: %s", entry->path);
|
||||||
|
}
|
||||||
|
|
||||||
|
zip_stat_t stat;
|
||||||
|
memoryZero(&stat, sizeof(zip_stat_t));
|
||||||
|
zip_stat_index(
|
||||||
|
ASSET_MANAGER.archive, (zip_uint64_t)idx, 0, &stat
|
||||||
|
);
|
||||||
|
|
||||||
|
if(!(stat.valid & ZIP_STAT_SIZE)) {
|
||||||
|
assetLoaderErrorThrow("Cannot stat JSON: %s", entry->path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(stat.size > ASSET_JSON_MAX_SIZE) {
|
||||||
|
assetLoaderErrorThrow("JSON too large: %s", entry->path);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size = (size_t)stat.size;
|
||||||
|
uint8_t *buf = (uint8_t *)memoryAllocate(size + 1);
|
||||||
|
|
||||||
|
zip_file_t *zf = zip_fopen_index(
|
||||||
|
ASSET_MANAGER.archive, (zip_uint64_t)idx, 0
|
||||||
|
);
|
||||||
|
if(!zf) {
|
||||||
|
memoryFree(buf);
|
||||||
|
assetLoaderErrorThrow(
|
||||||
|
"Failed to open ZIP entry: %s", entry->path
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
zip_fread(zf, buf, size);
|
||||||
|
zip_fclose(zf);
|
||||||
|
buf[size] = '\0';
|
||||||
|
|
||||||
|
entry->data.json.doc = yyjson_read(
|
||||||
|
(const char_t *)buf, size, 0
|
||||||
|
);
|
||||||
|
memoryFree(buf);
|
||||||
|
|
||||||
|
if(!entry->data.json.doc) {
|
||||||
|
assetLoaderErrorThrow("Failed to parse JSON: %s", entry->path);
|
||||||
|
}
|
||||||
|
|
||||||
|
entry->data.json.root = yyjson_doc_get_root(
|
||||||
|
entry->data.json.doc
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void assetJsonLoaderDispose(assetentry_t *entry) {
|
||||||
|
if(!entry->data.json.doc) return;
|
||||||
|
yyjson_doc_free(entry->data.json.doc);
|
||||||
|
entry->data.json.doc = NULL;
|
||||||
|
entry->data.json.root = NULL;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "asset/asset.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Async loader callback for JSON assets. Opens the archive entry,
|
||||||
|
* reads bytes, and parses with yyjson. Sets entry->data.json.doc
|
||||||
|
* and entry->data.json.root on success.
|
||||||
|
* @param entry The asset entry to load into.
|
||||||
|
*/
|
||||||
|
void assetJsonLoaderAsync(assetentry_t *entry);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispose callback for JSON asset entries. Frees the yyjson doc.
|
||||||
|
* @param entry The asset entry to dispose.
|
||||||
|
*/
|
||||||
|
void assetJsonLoaderDispose(assetentry_t *entry);
|
||||||
@@ -34,7 +34,11 @@ int_t renderPipelineCompare(const void *a, const void *b) {
|
|||||||
assertNotNull(b, "Entry b cannot be null");
|
assertNotNull(b, "Entry b cannot be null");
|
||||||
const renderpipelineentry_t *ea = (const renderpipelineentry_t *)a;
|
const renderpipelineentry_t *ea = (const renderpipelineentry_t *)a;
|
||||||
const renderpipelineentry_t *eb = (const renderpipelineentry_t *)b;
|
const renderpipelineentry_t *eb = (const renderpipelineentry_t *)b;
|
||||||
return (int_t)ea->effectivePriority - (int_t)eb->effectivePriority;
|
int_t pri = (int_t)ea->effectivePriority - (int_t)eb->effectivePriority;
|
||||||
|
if(pri != 0) return pri;
|
||||||
|
int_t aHasPos = (ea->posComp != COMPONENT_ID_INVALID) ? 1 : 0;
|
||||||
|
int_t bHasPos = (eb->posComp != COMPONENT_ID_INVALID) ? 1 : 0;
|
||||||
|
return aHasPos - bHasPos;
|
||||||
}
|
}
|
||||||
|
|
||||||
shader_t *renderPipelineGetShader(const entityrenderable_t *r) {
|
shader_t *renderPipelineGetShader(const entityrenderable_t *r) {
|
||||||
@@ -69,6 +73,9 @@ errorret_t renderPipeline(const entityid_t cameraId) {
|
|||||||
);
|
);
|
||||||
pipeline[i].entityId = entities[i];
|
pipeline[i].entityId = entities[i];
|
||||||
pipeline[i].componentId = components[i];
|
pipeline[i].componentId = components[i];
|
||||||
|
pipeline[i].posComp = entityGetComponent(
|
||||||
|
entities[i], COMPONENT_TYPE_POSITION
|
||||||
|
);
|
||||||
pipeline[i].effectivePriority = renderPipelineGetPriority(r);
|
pipeline[i].effectivePriority = renderPipelineGetPriority(r);
|
||||||
}
|
}
|
||||||
sort(
|
sort(
|
||||||
@@ -104,24 +111,30 @@ errorret_t renderPipeline(const entityid_t cameraId) {
|
|||||||
errorChain(shaderSetMatrix(s, SHADER_UNLIT_PROJECTION, proj));
|
errorChain(shaderSetMatrix(s, SHADER_UNLIT_PROJECTION, proj));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shader_t *prevShader = NULL;
|
||||||
|
bool_t prevNoPos = false;
|
||||||
for(entityid_t i = 0; i < entCount; i++) {
|
for(entityid_t i = 0; i < entCount; i++) {
|
||||||
entityid_t eid = pipeline[i].entityId;
|
entityid_t eid = pipeline[i].entityId;
|
||||||
componentid_t cid = pipeline[i].componentId;
|
componentid_t cid = pipeline[i].componentId;
|
||||||
|
componentid_t posComp = pipeline[i].posComp;
|
||||||
|
|
||||||
entityrenderable_t *r = componentGetData(
|
entityrenderable_t *r = componentGetData(
|
||||||
eid, cid, COMPONENT_TYPE_RENDERABLE
|
eid, cid, COMPONENT_TYPE_RENDERABLE
|
||||||
);
|
);
|
||||||
shader_t *s = renderPipelineGetShader(r);
|
shader_t *s = renderPipelineGetShader(r);
|
||||||
|
errorChain(shaderBind(s));
|
||||||
|
|
||||||
componentid_t posComp = entityGetComponent(eid, COMPONENT_TYPE_POSITION);
|
|
||||||
if(posComp == COMPONENT_ID_INVALID) {
|
if(posComp == COMPONENT_ID_INVALID) {
|
||||||
errorChain(shaderBind(s));
|
if(!prevNoPos || s != prevShader) {
|
||||||
errorChain(shaderSetMatrix(s, SHADER_UNLIT_MODEL, ident));
|
errorChain(shaderSetMatrix(s, SHADER_UNLIT_MODEL, ident));
|
||||||
|
}
|
||||||
|
prevNoPos = true;
|
||||||
} else {
|
} else {
|
||||||
entityPositionGetTransform(eid, posComp, model);
|
entityPositionGetTransform(eid, posComp, model);
|
||||||
errorChain(shaderBind(s));
|
|
||||||
errorChain(shaderSetMatrix(s, SHADER_UNLIT_MODEL, model));
|
errorChain(shaderSetMatrix(s, SHADER_UNLIT_MODEL, model));
|
||||||
|
prevNoPos = false;
|
||||||
}
|
}
|
||||||
|
prevShader = s;
|
||||||
|
|
||||||
errorChain(entityRenderableDraw(eid, cid));
|
errorChain(entityRenderableDraw(eid, cid));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,11 @@
|
|||||||
typedef struct {
|
typedef struct {
|
||||||
entityid_t entityId;
|
entityid_t entityId;
|
||||||
componentid_t componentId;
|
componentid_t componentId;
|
||||||
|
/**
|
||||||
|
* Precomputed position component ID, or COMPONENT_ID_INVALID if the entity
|
||||||
|
* has no position. Used for secondary sort and model-matrix batching.
|
||||||
|
*/
|
||||||
|
componentid_t posComp;
|
||||||
int8_t effectivePriority;
|
int8_t effectivePriority;
|
||||||
} renderpipelineentry_t;
|
} renderpipelineentry_t;
|
||||||
|
|
||||||
@@ -29,8 +34,11 @@ typedef struct {
|
|||||||
int8_t renderPipelineGetPriority(const entityrenderable_t *r);
|
int8_t renderPipelineGetPriority(const entityrenderable_t *r);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* sortcompare_t comparator for renderpipelineentry_t. Compares by
|
* sortcompare_t comparator for renderpipelineentry_t. Primary sort by
|
||||||
* effectivePriority ascending so lower-priority entries sort first.
|
* effectivePriority ascending. Secondary sort: entries with no position
|
||||||
|
* component (posComp == COMPONENT_ID_INVALID) sort before entries with a
|
||||||
|
* position component, grouping them to reduce redundant identity matrix
|
||||||
|
* uploads.
|
||||||
*
|
*
|
||||||
* @param a Pointer to the first renderpipelineentry_t.
|
* @param a Pointer to the first renderpipelineentry_t.
|
||||||
* @param b Pointer to the second renderpipelineentry_t.
|
* @param b Pointer to the second renderpipelineentry_t.
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#include "ui/ui.h"
|
#include "ui/ui.h"
|
||||||
#include "ui/uitextbox.h"
|
#include "ui/uitextbox.h"
|
||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
|
#include "asset/asset.h"
|
||||||
#include "entity/entitymanager.h"
|
#include "entity/entitymanager.h"
|
||||||
#include "entity/component/physics/entityphysics.h"
|
#include "entity/component/physics/entityphysics.h"
|
||||||
#include "physics/physicsmanager.h"
|
#include "physics/physicsmanager.h"
|
||||||
@@ -22,6 +23,7 @@
|
|||||||
#include "console/console.h"
|
#include "console/console.h"
|
||||||
#include "item/backpack.h"
|
#include "item/backpack.h"
|
||||||
#include "save/save.h"
|
#include "save/save.h"
|
||||||
|
#include "scene/scene/initial/initialscene.h"
|
||||||
|
|
||||||
engine_t ENGINE;
|
engine_t ENGINE;
|
||||||
|
|
||||||
@@ -39,6 +41,7 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
|||||||
consoleInit();
|
consoleInit();
|
||||||
errorChain(inputInit());
|
errorChain(inputInit());
|
||||||
// errorChain(saveInit());
|
// errorChain(saveInit());
|
||||||
|
errorChain(assetInit());
|
||||||
errorChain(localeManagerInit());
|
errorChain(localeManagerInit());
|
||||||
errorChain(displayInit());
|
errorChain(displayInit());
|
||||||
errorChain(uiInit());
|
errorChain(uiInit());
|
||||||
@@ -46,6 +49,7 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
|||||||
entityManagerInit();
|
entityManagerInit();
|
||||||
backpackInit();
|
backpackInit();
|
||||||
physicsManagerInit();
|
physicsManagerInit();
|
||||||
|
errorChain(initialSceneInit());
|
||||||
errorChain(networkInit());
|
errorChain(networkInit());
|
||||||
|
|
||||||
consolePrint("Engine initialized");
|
consolePrint("Engine initialized");
|
||||||
@@ -55,6 +59,7 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
|||||||
|
|
||||||
errorret_t engineUpdate(void) {
|
errorret_t engineUpdate(void) {
|
||||||
// Order here is important.
|
// Order here is important.
|
||||||
|
assetUpdate();
|
||||||
errorChain(networkUpdate());
|
errorChain(networkUpdate());
|
||||||
timeUpdate();
|
timeUpdate();
|
||||||
inputUpdate();
|
inputUpdate();
|
||||||
@@ -82,6 +87,7 @@ errorret_t engineDispose(void) {
|
|||||||
consoleDispose();
|
consoleDispose();
|
||||||
errorChain(displayDispose());
|
errorChain(displayDispose());
|
||||||
// errorChain(saveDispose());
|
// errorChain(saveDispose());
|
||||||
|
errorChain(assetDispose());
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
entity.c
|
entity.c
|
||||||
entitymanager.c
|
entitymanager.c
|
||||||
component.c
|
component.c
|
||||||
|
entityjson.c
|
||||||
)
|
)
|
||||||
|
|
||||||
# Subdirs
|
# Subdirs
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "entityjson.h"
|
||||||
|
#include "entity/entitymanager.h"
|
||||||
|
#include "entity/entity.h"
|
||||||
|
#include "entity/component.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
|
||||||
|
void entitySerialize(
|
||||||
|
const entityid_t entityId,
|
||||||
|
yyjson_mut_doc *doc,
|
||||||
|
yyjson_mut_val *obj
|
||||||
|
) {
|
||||||
|
assertNotNull(doc, "JSON doc cannot be null");
|
||||||
|
assertNotNull(obj, "JSON obj cannot be null");
|
||||||
|
assertTrue(entityId < ENTITY_COUNT_MAX, "Entity ID OOB");
|
||||||
|
|
||||||
|
yyjson_mut_val *comps = yyjson_mut_obj(doc);
|
||||||
|
yyjson_mut_obj_add_val(doc, obj, "components", comps);
|
||||||
|
|
||||||
|
for(componentid_t cid = 0; cid < ENTITY_COMPONENT_COUNT_MAX; cid++) {
|
||||||
|
componentindex_t idx = componentGetIndex(entityId, cid);
|
||||||
|
component_t *cmp = &ENTITY_MANAGER.components[idx];
|
||||||
|
if(cmp->type == COMPONENT_TYPE_NULL) continue;
|
||||||
|
if(!COMPONENT_DEFINITIONS[cmp->type].serialize) continue;
|
||||||
|
|
||||||
|
const char_t *name = COMPONENT_DEFINITIONS[cmp->type].name;
|
||||||
|
yyjson_mut_val *compObj = yyjson_mut_obj(doc);
|
||||||
|
COMPONENT_DEFINITIONS[cmp->type].serialize(
|
||||||
|
entityId, cid, doc, compObj
|
||||||
|
);
|
||||||
|
yyjson_mut_obj_add_val(doc, comps, name, compObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t entityDeserialize(
|
||||||
|
const entityid_t entityId,
|
||||||
|
yyjson_val *obj
|
||||||
|
) {
|
||||||
|
assertTrue(entityId < ENTITY_COUNT_MAX, "Entity ID OOB");
|
||||||
|
assertNotNull(obj, "JSON obj cannot be null");
|
||||||
|
|
||||||
|
yyjson_val *comps = yyjson_obj_get(obj, "components");
|
||||||
|
if(!comps || !yyjson_is_obj(comps)) errorOk();
|
||||||
|
|
||||||
|
for(componenttype_t t = 1; t < COMPONENT_TYPE_COUNT; t++) {
|
||||||
|
const char_t *name = COMPONENT_DEFINITIONS[t].name;
|
||||||
|
if(!name) continue;
|
||||||
|
yyjson_val *compObj = yyjson_obj_get(comps, name);
|
||||||
|
if(!compObj) continue;
|
||||||
|
componentid_t cid = entityAddComponent(entityId, t);
|
||||||
|
errorChain(componentDeserialize(entityId, cid, compObj));
|
||||||
|
}
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "entity/entitybase.h"
|
||||||
|
#include "error/error.h"
|
||||||
|
#include <yyjson.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes all components of an entity into a JSON object. Each
|
||||||
|
* component is written as a nested object under its lowercase name
|
||||||
|
* (e.g. "position", "camera", "renderable"). Components with no
|
||||||
|
* serialize callback are silently skipped.
|
||||||
|
*
|
||||||
|
* @param entityId The entity to serialize.
|
||||||
|
* @param doc The mutable JSON document to allocate values from.
|
||||||
|
* @param obj The JSON object to write into.
|
||||||
|
*/
|
||||||
|
void entitySerialize(
|
||||||
|
const entityid_t entityId,
|
||||||
|
yyjson_mut_doc *doc,
|
||||||
|
yyjson_mut_val *obj
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deserializes an entity from a JSON object. Reads the "components"
|
||||||
|
* object and, for each recognized component name, adds the component
|
||||||
|
* and deserializes its data. Components present in the JSON but not
|
||||||
|
* in COMPONENT_DEFINITIONS are silently ignored.
|
||||||
|
*
|
||||||
|
* @param entityId The entity to deserialize into (must be initialized).
|
||||||
|
* @param obj The JSON object to read from.
|
||||||
|
* @return Error state.
|
||||||
|
*/
|
||||||
|
errorret_t entityDeserialize(
|
||||||
|
const entityid_t entityId,
|
||||||
|
yyjson_val *obj
|
||||||
|
);
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# Copyright (c) 2026 Dominic Masters
|
||||||
|
#
|
||||||
|
# This software is released under the MIT License.
|
||||||
|
# https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
add_subdirectory(scene)
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# Copyright (c) 2026 Dominic Masters
|
||||||
|
#
|
||||||
|
# This software is released under the MIT License.
|
||||||
|
# https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
add_subdirectory(initial)
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2026 Dominic Masters
|
||||||
|
#
|
||||||
|
# This software is released under the MIT License.
|
||||||
|
# https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||||
|
PUBLIC
|
||||||
|
initialscene.c
|
||||||
|
)
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "initialscene.h"
|
||||||
|
#include "asset/asset.h"
|
||||||
|
#include "entity/entitymanager.h"
|
||||||
|
#include "entity/entity.h"
|
||||||
|
#include "entity/entityjson.h"
|
||||||
|
#include "entity/component/display/entityposition.h"
|
||||||
|
#include "entity/component/display/entitycamera.h"
|
||||||
|
|
||||||
|
errorret_t initialSceneInit(void) {
|
||||||
|
entityid_t camId = entityManagerAdd();
|
||||||
|
entityInit(camId);
|
||||||
|
componentid_t camPos = entityAddComponent(
|
||||||
|
camId, COMPONENT_TYPE_POSITION
|
||||||
|
);
|
||||||
|
entityAddComponent(camId, COMPONENT_TYPE_CAMERA);
|
||||||
|
|
||||||
|
vec3 eye = {0.0f, 2.0f, 5.0f};
|
||||||
|
vec3 target = {0.5f, 0.5f, 0.5f};
|
||||||
|
vec3 up = {0.0f, 1.0f, 0.0f};
|
||||||
|
entityPositionLookAt(camId, camPos, eye, target, up);
|
||||||
|
|
||||||
|
assetentry_t *entry = assetGetEntry(
|
||||||
|
"cube.json", ASSET_LOADER_TYPE_JSON, NULL
|
||||||
|
);
|
||||||
|
if(!entry) errorThrow("Failed to get asset entry for cube.json");
|
||||||
|
|
||||||
|
assetLock(entry);
|
||||||
|
errorret_t ret = assetRequireLoaded(entry);
|
||||||
|
if(errorIsOk(ret)) {
|
||||||
|
entityid_t cubeId = entityManagerAdd();
|
||||||
|
entityInit(cubeId);
|
||||||
|
ret = entityDeserialize(cubeId, entry->data.json.root);
|
||||||
|
}
|
||||||
|
assetUnlock(entry);
|
||||||
|
errorChain(ret);
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "error/error.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the initial test scene: a camera entity built in C and
|
||||||
|
* a cube entity deserialized from cube.json via the asset loader.
|
||||||
|
* @returns errorret_t
|
||||||
|
*/
|
||||||
|
errorret_t initialSceneInit(void);
|
||||||
+2
-1
@@ -10,4 +10,5 @@ add_subdirectory(display)
|
|||||||
# add_subdirectory(rpg)
|
# add_subdirectory(rpg)
|
||||||
# add_subdirectory(item)
|
# add_subdirectory(item)
|
||||||
add_subdirectory(time)
|
add_subdirectory(time)
|
||||||
add_subdirectory(util)
|
add_subdirectory(util)
|
||||||
|
add_subdirectory(entity)
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# Copyright (c) 2026 Dominic Masters
|
||||||
|
#
|
||||||
|
# This software is released under the MIT License.
|
||||||
|
# https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
include(dusktest)
|
||||||
|
dusktest(test_entityjson.c)
|
||||||
|
target_compile_definitions(test_entityjson PRIVATE
|
||||||
|
DUSK_ENTITY_JSON_PATH="${CMAKE_SOURCE_DIR}/assets/entity.json"
|
||||||
|
)
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "dusktest.h"
|
||||||
|
#include "entity/entitymanager.h"
|
||||||
|
#include "entity/entity.h"
|
||||||
|
#include "entity/entityjson.h"
|
||||||
|
#include "entity/component.h"
|
||||||
|
#include "entity/component/display/entityposition.h"
|
||||||
|
#include "entity/component/display/entitycamera.h"
|
||||||
|
#include "entity/component/display/entityrenderable.h"
|
||||||
|
#include "entity/component/physics/entityphysics.h"
|
||||||
|
#include "entity/component/trigger/entitytrigger.h"
|
||||||
|
#include "util/memory.h"
|
||||||
|
#include "log/log.h"
|
||||||
|
#include <yyjson.h>
|
||||||
|
|
||||||
|
static void test_entityjson_load(void **state) {
|
||||||
|
yyjson_read_err readErr;
|
||||||
|
yyjson_doc *doc = yyjson_read_file(
|
||||||
|
DUSK_ENTITY_JSON_PATH, 0, NULL, &readErr
|
||||||
|
);
|
||||||
|
assert_non_null(doc);
|
||||||
|
|
||||||
|
yyjson_val *root = yyjson_doc_get_root(doc);
|
||||||
|
assert_non_null(root);
|
||||||
|
|
||||||
|
entityid_t entId = entityManagerAdd();
|
||||||
|
entityInit(entId);
|
||||||
|
|
||||||
|
errorret_t ret = entityDeserialize(entId, root);
|
||||||
|
assert_true(errorIsOk(ret));
|
||||||
|
|
||||||
|
logDebug("Loaded entity from: %s\n", DUSK_ENTITY_JSON_PATH);
|
||||||
|
|
||||||
|
// Position
|
||||||
|
componentid_t posComp = entityGetComponent(entId, COMPONENT_TYPE_POSITION);
|
||||||
|
assert_int_not_equal(posComp, COMPONENT_ID_INVALID);
|
||||||
|
entityposition_t *pos = componentGetData(
|
||||||
|
entId, posComp, COMPONENT_TYPE_POSITION
|
||||||
|
);
|
||||||
|
logDebug(" position:\n");
|
||||||
|
logDebug(
|
||||||
|
" position: [%f, %f, %f]\n",
|
||||||
|
pos->position[0], pos->position[1], pos->position[2]
|
||||||
|
);
|
||||||
|
logDebug(
|
||||||
|
" rotation: [%f, %f, %f]\n",
|
||||||
|
pos->rotation[0], pos->rotation[1], pos->rotation[2]
|
||||||
|
);
|
||||||
|
logDebug(
|
||||||
|
" scale: [%f, %f, %f]\n",
|
||||||
|
pos->scale[0], pos->scale[1], pos->scale[2]
|
||||||
|
);
|
||||||
|
assert_float_equal(pos->position[0], 0.0f, 0.0001f);
|
||||||
|
assert_float_equal(pos->position[1], 0.0f, 0.0001f);
|
||||||
|
assert_float_equal(pos->position[2], 0.0f, 0.0001f);
|
||||||
|
assert_float_equal(pos->scale[0], 1.0f, 0.0001f);
|
||||||
|
assert_float_equal(pos->scale[1], 1.0f, 0.0001f);
|
||||||
|
assert_float_equal(pos->scale[2], 1.0f, 0.0001f);
|
||||||
|
|
||||||
|
// Camera
|
||||||
|
componentid_t camComp = entityGetComponent(entId, COMPONENT_TYPE_CAMERA);
|
||||||
|
assert_int_not_equal(camComp, COMPONENT_ID_INVALID);
|
||||||
|
entitycamera_t *cam = componentGetData(
|
||||||
|
entId, camComp, COMPONENT_TYPE_CAMERA
|
||||||
|
);
|
||||||
|
const char_t *projStr = "orthographic";
|
||||||
|
if(cam->projType == ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE) {
|
||||||
|
projStr = "perspective";
|
||||||
|
} else if(cam->projType == ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED) {
|
||||||
|
projStr = "perspective_flipped";
|
||||||
|
}
|
||||||
|
logDebug(" camera:\n");
|
||||||
|
logDebug(" projType: %s\n", projStr);
|
||||||
|
logDebug(" fov: %f\n", cam->perspective.fov);
|
||||||
|
logDebug(" nearClip: %f\n", cam->nearClip);
|
||||||
|
logDebug(" farClip: %f\n", cam->farClip);
|
||||||
|
assert_int_equal(
|
||||||
|
cam->projType, ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE
|
||||||
|
);
|
||||||
|
assert_float_equal(cam->perspective.fov, 45.0f, 0.0001f);
|
||||||
|
assert_float_equal(cam->nearClip, 0.1f, 0.0001f);
|
||||||
|
assert_float_equal(cam->farClip, 5000.0f, 0.1f);
|
||||||
|
|
||||||
|
// Renderable
|
||||||
|
componentid_t rendComp = entityGetComponent(
|
||||||
|
entId, COMPONENT_TYPE_RENDERABLE
|
||||||
|
);
|
||||||
|
assert_int_not_equal(rendComp, COMPONENT_ID_INVALID);
|
||||||
|
entityrenderable_t *rend = componentGetData(
|
||||||
|
entId, rendComp, COMPONENT_TYPE_RENDERABLE
|
||||||
|
);
|
||||||
|
const char_t *rendTypeStr = "custom";
|
||||||
|
if(rend->type == ENTITY_RENDERABLE_TYPE_SPRITEBATCH) {
|
||||||
|
rendTypeStr = "spritebatch";
|
||||||
|
} else if(rend->type == ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL) {
|
||||||
|
rendTypeStr = "shader_material";
|
||||||
|
}
|
||||||
|
logDebug(" renderable:\n");
|
||||||
|
logDebug(" type: %s\n", rendTypeStr);
|
||||||
|
logDebug(" priority: %d\n", (int_t)rend->priority);
|
||||||
|
logDebug(
|
||||||
|
" shaderType: %d\n", (int_t)rend->data.material.shaderType
|
||||||
|
);
|
||||||
|
logDebug(
|
||||||
|
" stateFlags: %d\n", (int_t)rend->data.material.state.flags
|
||||||
|
);
|
||||||
|
assert_int_equal(rend->type, ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL);
|
||||||
|
assert_int_equal(rend->priority, 0);
|
||||||
|
|
||||||
|
// Physics
|
||||||
|
componentid_t physComp = entityGetComponent(entId, COMPONENT_TYPE_PHYSICS);
|
||||||
|
assert_int_not_equal(physComp, COMPONENT_ID_INVALID);
|
||||||
|
entityphysics_t *phys = componentGetData(
|
||||||
|
entId, physComp, COMPONENT_TYPE_PHYSICS
|
||||||
|
);
|
||||||
|
const char_t *bodyStr = "static";
|
||||||
|
if(phys->type == PHYSICS_BODY_DYNAMIC) {
|
||||||
|
bodyStr = "dynamic";
|
||||||
|
} else if(phys->type == PHYSICS_BODY_KINEMATIC) {
|
||||||
|
bodyStr = "kinematic";
|
||||||
|
}
|
||||||
|
const char_t *shapeStr = "cube";
|
||||||
|
if(phys->shape.type == PHYSICS_SHAPE_SPHERE) {
|
||||||
|
shapeStr = "sphere";
|
||||||
|
} else if(phys->shape.type == PHYSICS_SHAPE_CAPSULE) {
|
||||||
|
shapeStr = "capsule";
|
||||||
|
} else if(phys->shape.type == PHYSICS_SHAPE_PLANE) {
|
||||||
|
shapeStr = "plane";
|
||||||
|
}
|
||||||
|
logDebug(" physics:\n");
|
||||||
|
logDebug(" bodyType: %s\n", bodyStr);
|
||||||
|
logDebug(" shapeType: %s\n", shapeStr);
|
||||||
|
logDebug(" gravityScale: %f\n", phys->gravityScale);
|
||||||
|
assert_int_equal(phys->type, PHYSICS_BODY_DYNAMIC);
|
||||||
|
assert_int_equal(phys->shape.type, PHYSICS_SHAPE_CUBE);
|
||||||
|
assert_float_equal(phys->gravityScale, 1.0f, 0.0001f);
|
||||||
|
|
||||||
|
// Trigger
|
||||||
|
componentid_t trigComp = entityGetComponent(
|
||||||
|
entId, COMPONENT_TYPE_TRIGGER
|
||||||
|
);
|
||||||
|
assert_int_not_equal(trigComp, COMPONENT_ID_INVALID);
|
||||||
|
entitytrigger_t *trig = componentGetData(
|
||||||
|
entId, trigComp, COMPONENT_TYPE_TRIGGER
|
||||||
|
);
|
||||||
|
logDebug(" trigger:\n");
|
||||||
|
logDebug(
|
||||||
|
" min: [%f, %f, %f]\n",
|
||||||
|
trig->min[0], trig->min[1], trig->min[2]
|
||||||
|
);
|
||||||
|
logDebug(
|
||||||
|
" max: [%f, %f, %f]\n",
|
||||||
|
trig->max[0], trig->max[1], trig->max[2]
|
||||||
|
);
|
||||||
|
assert_float_equal(trig->min[0], -0.5f, 0.0001f);
|
||||||
|
assert_float_equal(trig->max[0], 0.5f, 0.0001f);
|
||||||
|
|
||||||
|
entityDispose(entId);
|
||||||
|
yyjson_doc_free(doc);
|
||||||
|
|
||||||
|
assert_int_equal(memoryGetAllocatedCount(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int groupSetup(void **state) {
|
||||||
|
assertInit();
|
||||||
|
entityManagerInit();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int groupTeardown(void **state) {
|
||||||
|
entityManagerDispose();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
const struct CMUnitTest tests[] = {
|
||||||
|
cmocka_unit_test(test_entityjson_load),
|
||||||
|
};
|
||||||
|
return cmocka_run_group_tests(tests, groupSetup, groupTeardown);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user