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_package(SDL2 REQUIRED)
|
||||
find_package(OpenGL REQUIRED)
|
||||
find_package(LIBZIP REQUIRED)
|
||||
# find_package(CURL REQUIRED)
|
||||
|
||||
# Setup endianess at compile time to optimize.
|
||||
@@ -23,6 +24,7 @@ target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
||||
OpenGL::GL
|
||||
GL
|
||||
m
|
||||
libzip::zip
|
||||
# CURL::libcurl
|
||||
)
|
||||
|
||||
|
||||
@@ -57,3 +57,5 @@ add_subdirectory(overworld)
|
||||
add_subdirectory(save)
|
||||
add_subdirectory(util)
|
||||
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");
|
||||
const renderpipelineentry_t *ea = (const renderpipelineentry_t *)a;
|
||||
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) {
|
||||
@@ -69,6 +73,9 @@ errorret_t renderPipeline(const entityid_t cameraId) {
|
||||
);
|
||||
pipeline[i].entityId = entities[i];
|
||||
pipeline[i].componentId = components[i];
|
||||
pipeline[i].posComp = entityGetComponent(
|
||||
entities[i], COMPONENT_TYPE_POSITION
|
||||
);
|
||||
pipeline[i].effectivePriority = renderPipelineGetPriority(r);
|
||||
}
|
||||
sort(
|
||||
@@ -104,24 +111,30 @@ errorret_t renderPipeline(const entityid_t cameraId) {
|
||||
errorChain(shaderSetMatrix(s, SHADER_UNLIT_PROJECTION, proj));
|
||||
}
|
||||
|
||||
shader_t *prevShader = NULL;
|
||||
bool_t prevNoPos = false;
|
||||
for(entityid_t i = 0; i < entCount; i++) {
|
||||
entityid_t eid = pipeline[i].entityId;
|
||||
componentid_t cid = pipeline[i].componentId;
|
||||
componentid_t posComp = pipeline[i].posComp;
|
||||
|
||||
entityrenderable_t *r = componentGetData(
|
||||
eid, cid, COMPONENT_TYPE_RENDERABLE
|
||||
);
|
||||
shader_t *s = renderPipelineGetShader(r);
|
||||
errorChain(shaderBind(s));
|
||||
|
||||
componentid_t posComp = entityGetComponent(eid, COMPONENT_TYPE_POSITION);
|
||||
if(posComp == COMPONENT_ID_INVALID) {
|
||||
errorChain(shaderBind(s));
|
||||
errorChain(shaderSetMatrix(s, SHADER_UNLIT_MODEL, ident));
|
||||
if(!prevNoPos || s != prevShader) {
|
||||
errorChain(shaderSetMatrix(s, SHADER_UNLIT_MODEL, ident));
|
||||
}
|
||||
prevNoPos = true;
|
||||
} else {
|
||||
entityPositionGetTransform(eid, posComp, model);
|
||||
errorChain(shaderBind(s));
|
||||
errorChain(shaderSetMatrix(s, SHADER_UNLIT_MODEL, model));
|
||||
prevNoPos = false;
|
||||
}
|
||||
prevShader = s;
|
||||
|
||||
errorChain(entityRenderableDraw(eid, cid));
|
||||
}
|
||||
|
||||
@@ -14,6 +14,11 @@
|
||||
typedef struct {
|
||||
entityid_t entityId;
|
||||
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;
|
||||
} renderpipelineentry_t;
|
||||
|
||||
@@ -29,8 +34,11 @@ typedef struct {
|
||||
int8_t renderPipelineGetPriority(const entityrenderable_t *r);
|
||||
|
||||
/**
|
||||
* sortcompare_t comparator for renderpipelineentry_t. Compares by
|
||||
* effectivePriority ascending so lower-priority entries sort first.
|
||||
* sortcompare_t comparator for renderpipelineentry_t. Primary sort by
|
||||
* 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 b Pointer to the second renderpipelineentry_t.
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "ui/ui.h"
|
||||
#include "ui/uitextbox.h"
|
||||
#include "assert/assert.h"
|
||||
#include "asset/asset.h"
|
||||
#include "entity/entitymanager.h"
|
||||
#include "entity/component/physics/entityphysics.h"
|
||||
#include "physics/physicsmanager.h"
|
||||
@@ -22,6 +23,7 @@
|
||||
#include "console/console.h"
|
||||
#include "item/backpack.h"
|
||||
#include "save/save.h"
|
||||
#include "scene/scene/initial/initialscene.h"
|
||||
|
||||
engine_t ENGINE;
|
||||
|
||||
@@ -39,6 +41,7 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
||||
consoleInit();
|
||||
errorChain(inputInit());
|
||||
// errorChain(saveInit());
|
||||
errorChain(assetInit());
|
||||
errorChain(localeManagerInit());
|
||||
errorChain(displayInit());
|
||||
errorChain(uiInit());
|
||||
@@ -46,6 +49,7 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
||||
entityManagerInit();
|
||||
backpackInit();
|
||||
physicsManagerInit();
|
||||
errorChain(initialSceneInit());
|
||||
errorChain(networkInit());
|
||||
|
||||
consolePrint("Engine initialized");
|
||||
@@ -55,6 +59,7 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
||||
|
||||
errorret_t engineUpdate(void) {
|
||||
// Order here is important.
|
||||
assetUpdate();
|
||||
errorChain(networkUpdate());
|
||||
timeUpdate();
|
||||
inputUpdate();
|
||||
@@ -82,6 +87,7 @@ errorret_t engineDispose(void) {
|
||||
consoleDispose();
|
||||
errorChain(displayDispose());
|
||||
// errorChain(saveDispose());
|
||||
errorChain(assetDispose());
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
entity.c
|
||||
entitymanager.c
|
||||
component.c
|
||||
entityjson.c
|
||||
)
|
||||
|
||||
# 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);
|
||||
@@ -11,3 +11,4 @@ add_subdirectory(display)
|
||||
# add_subdirectory(item)
|
||||
add_subdirectory(time)
|
||||
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