Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2849ff8844 | |||
| 9f3089742a | |||
| b286a9bbcd | |||
| 6204e745ba | |||
| bbe0e48d23 | |||
| 79054080c0 | |||
| 81024c4c09 | |||
| 9068d96130 | |||
| 6f47543720 | |||
| 5a08384ae1 | |||
| 45d8fda0e4 | |||
| a9e664492f | |||
| 3c8b6cb2cc | |||
| 2b3abbe13b | |||
| 241a52b94a | |||
| 82c300b077 | |||
| 0f8b629e20 | |||
| 36f6ac65f2 | |||
| a25871a849 | |||
| 57766a9104 | |||
| 3770ae1645 | |||
| d73edb403f | |||
| b14196ff0d |
@@ -144,7 +144,7 @@ jobs:
|
|||||||
- name: Copy output files.
|
- name: Copy output files.
|
||||||
run: |
|
run: |
|
||||||
mkdir -p ./git-artifcats/Dusk/apps/Dusk
|
mkdir -p ./git-artifcats/Dusk/apps/Dusk
|
||||||
cp build-wii/Dusk.dol ./git-artifcats/Dusk/apps/Dusk/boot.dol
|
cp build-wii/boot.dol ./git-artifcats/Dusk/apps/Dusk/boot.dol
|
||||||
cp build-wii/dusk.dsk ./git-artifcats/Dusk/apps/Dusk/dusk.dsk
|
cp build-wii/dusk.dsk ./git-artifcats/Dusk/apps/Dusk/dusk.dsk
|
||||||
cp build-wii/meta.xml ./git-artifcats/Dusk/apps/Dusk/meta.xml
|
cp build-wii/meta.xml ./git-artifcats/Dusk/apps/Dusk/meta.xml
|
||||||
- name: Upload Wii binary
|
- name: Upload Wii binary
|
||||||
|
|||||||
@@ -106,3 +106,4 @@ yarn.lock
|
|||||||
/build*
|
/build*
|
||||||
/assets/test
|
/assets/test
|
||||||
/tools_old
|
/tools_old
|
||||||
|
/assets/test.png
|
||||||
+1
-4
@@ -1,5 +1,3 @@
|
|||||||
Console.print('This is called from JavaScript');
|
|
||||||
|
|
||||||
const platformNames = {
|
const platformNames = {
|
||||||
[System.PLATFORM_LINUX]: 'Linux',
|
[System.PLATFORM_LINUX]: 'Linux',
|
||||||
[System.PLATFORM_KNULLI]: 'Knulli',
|
[System.PLATFORM_KNULLI]: 'Knulli',
|
||||||
@@ -8,5 +6,4 @@ const platformNames = {
|
|||||||
[System.PLATFORM_WII]: 'Wii',
|
[System.PLATFORM_WII]: 'Wii',
|
||||||
};
|
};
|
||||||
|
|
||||||
const platformName = platformNames[System.platform] || 'Unknown';
|
Console.print('Platform: ' + (platformNames[System.platform] || 'Unknown'));
|
||||||
Console.print('Platform: ' + platformName);
|
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
Console.print('testscene.js is loaded');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
test: function() {
|
||||||
|
return 'Hello string';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// var scene = {
|
||||||
|
// 'test': 'teststring'
|
||||||
|
// };
|
||||||
|
|
||||||
|
// var assets = AssetBatch([
|
||||||
|
// { path: 'test.png', type: Asset.TYPE_TEXTURE, format: Texture.FORMAT_RGBA }
|
||||||
|
// ]);
|
||||||
|
|
||||||
|
// var cam;
|
||||||
|
// var camPos;
|
||||||
|
// var testEntity;
|
||||||
|
// var testPos;
|
||||||
|
// var testRenderable;
|
||||||
|
// var texEntry;
|
||||||
|
|
||||||
|
// scene.init = function() {
|
||||||
|
// assets.lock();
|
||||||
|
// assets.onLoaded[0] = scene.loaded;
|
||||||
|
// };
|
||||||
|
|
||||||
|
// scene.loaded = function() {
|
||||||
|
// texEntry = assets.entry(0);
|
||||||
|
|
||||||
|
// // Camera at (3, 3, 3) looking at origin
|
||||||
|
// cam = Entity.create();
|
||||||
|
// camPos = cam.add(Component.POSITION);
|
||||||
|
// cam.add(Component.CAMERA);
|
||||||
|
// camPos.localPosition = new Vec3(3, 3, 3);
|
||||||
|
// camPos.lookAt(new Vec3(0, 0, 0));
|
||||||
|
|
||||||
|
// // Test entity with textured quad at origin
|
||||||
|
// testEntity = Entity.create();
|
||||||
|
// testPos = testEntity.add(Component.POSITION);
|
||||||
|
// testRenderable = testEntity.add(Component.RENDERABLE);
|
||||||
|
|
||||||
|
// testRenderable.texture = texEntry.texture;
|
||||||
|
// testRenderable.sprites = [
|
||||||
|
// [0, 0, 1, 1, 0, 1, 1, 0]
|
||||||
|
// ];
|
||||||
|
// testPos.localPosition = new Vec3(0, 0, 0);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// scene.dispose = function() {
|
||||||
|
// Console.print('Scene Dispose');
|
||||||
|
// Entity.dispose(cam);
|
||||||
|
// Entity.dispose(testEntity);
|
||||||
|
// assets.unlock();
|
||||||
|
// };
|
||||||
|
|
||||||
|
// module.exports = scene;
|
||||||
@@ -1,15 +1,18 @@
|
|||||||
# Build type: FAT (SD/USB via libfat) or ISO (DVD disc via libogc DVD driver)
|
# Build type: DOL (SD/USB via libfat) or ISO (DVD disc via libogc DVD driver)
|
||||||
set(DUSK_DOLPHIN_BUILD_TYPE "FAT" CACHE STRING "Dolphin asset source: FAT (SD/USB) or ISO (DVD disc)")
|
set(DUSK_DOLPHIN_BUILD_TYPE "DOL" CACHE STRING "Dolphin asset source: DOL (SD/USB) or ISO (DVD disc)")
|
||||||
set_property(CACHE DUSK_DOLPHIN_BUILD_TYPE PROPERTY STRINGS "FAT" "ISO")
|
set_property(CACHE DUSK_DOLPHIN_BUILD_TYPE PROPERTY STRINGS "DOL" "ISO")
|
||||||
|
|
||||||
# Target definitions
|
# Numeric tokens so #if DUSK_DOLPHIN_BUILD_TYPE == DOL works in C.
|
||||||
|
# DUSK_DOLPHIN_BUILD_TYPE is passed without quotes so it expands to the identifier.
|
||||||
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
||||||
DUSK_DOLPHIN
|
DUSK_DOLPHIN
|
||||||
DUSK_INPUT_GAMEPAD
|
DUSK_INPUT_GAMEPAD
|
||||||
DUSK_DISPLAY_WIDTH=640
|
DUSK_DISPLAY_WIDTH=640
|
||||||
DUSK_DISPLAY_HEIGHT=480
|
DUSK_DISPLAY_HEIGHT=480
|
||||||
DUSK_THREAD_PTHREAD
|
DUSK_THREAD_PTHREAD
|
||||||
DUSK_DOLPHIN_BUILD_TYPE="${DUSK_DOLPHIN_BUILD_TYPE}"
|
DOL=1
|
||||||
|
ISO=2
|
||||||
|
DUSK_DOLPHIN_BUILD_TYPE=${DUSK_DOLPHIN_BUILD_TYPE}
|
||||||
)
|
)
|
||||||
|
|
||||||
# Custom compiler flags
|
# Custom compiler flags
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
docker build -t dusk-dolphin -f docker/dolphin/Dockerfile .
|
docker build -t dusk-dolphin -f docker/dolphin/Dockerfile .
|
||||||
docker run --rm -v $(pwd):/workdir dusk-dolphin /bin/bash -c "./scripts/build-gamecube.sh"
|
docker run --rm -v "$(pwd):/workdir" dusk-dolphin /bin/bash -c "./scripts/build-gamecube.sh"
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
docker build -t dusk-dolphin -f docker/dolphin/Dockerfile .
|
docker build -t dusk-dolphin -f docker/dolphin/Dockerfile .
|
||||||
docker run --rm -v $(pwd):/workdir dusk-dolphin /bin/bash -c "./scripts/build-gamecube-iso.sh"
|
docker run --rm -v "$(pwd):/workdir" dusk-dolphin /bin/bash -c "./scripts/build-gamecube-iso.sh"
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ fi
|
|||||||
mkdir -p build-gamecube
|
mkdir -p build-gamecube
|
||||||
cmake -S. -Bbuild-gamecube \
|
cmake -S. -Bbuild-gamecube \
|
||||||
-DDUSK_TARGET_SYSTEM=gamecube \
|
-DDUSK_TARGET_SYSTEM=gamecube \
|
||||||
|
-DDUSK_DOLPHIN_BUILD_TYPE=DOL \
|
||||||
-DCMAKE_TOOLCHAIN_FILE="$DEVKITPRO/cmake/GameCube.cmake" \
|
-DCMAKE_TOOLCHAIN_FILE="$DEVKITPRO/cmake/GameCube.cmake" \
|
||||||
-DDKP_OGC_PLATFORM_LIBRARY=libogc2
|
-DDKP_OGC_PLATFORM_LIBRARY=libogc2
|
||||||
cd build-gamecube
|
cd build-gamecube
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
docker build -t dusk-knulli -f docker/knulli/Dockerfile .
|
docker build -t dusk-knulli -f docker/knulli/Dockerfile .
|
||||||
docker run --rm -v $(pwd):/workdir dusk-knulli /bin/bash -c "./scripts/build-knulli.sh"
|
docker run --rm -v "$(pwd):/workdir" dusk-knulli /bin/bash -c "./scripts/build-knulli.sh"
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
docker build -t dusk-linux -f docker/linux/Dockerfile .
|
docker build -t dusk-linux -f docker/linux/Dockerfile .
|
||||||
docker run --rm -v $(pwd):/workdir dusk-linux /bin/bash -c "./scripts/build-linux.sh"
|
docker run --rm -v "$(pwd):/workdir" dusk-linux /bin/bash -c "./scripts/build-linux.sh"
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
docker build -t dusk-psp -f docker/psp/Dockerfile .
|
docker build -t dusk-psp -f docker/psp/Dockerfile .
|
||||||
docker run --rm -v $(pwd):/workdir dusk-psp /bin/bash -c "./scripts/build-psp.sh"
|
docker run --rm -v "$(pwd):/workdir" dusk-psp /bin/bash -c "./scripts/build-psp.sh"
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
docker build -t dusk-vita -f docker/vita/Dockerfile .
|
docker build -t dusk-vita -f docker/vita/Dockerfile .
|
||||||
docker run --rm -v $(pwd):/workdir dusk-vita /bin/bash -c "./scripts/build-vita.sh"
|
docker run --rm -v "$(pwd):/workdir" dusk-vita /bin/bash -c "./scripts/build-vita.sh"
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
docker build -t dusk-dolphin -f docker/dolphin/Dockerfile .
|
docker build -t dusk-dolphin -f docker/dolphin/Dockerfile .
|
||||||
docker run --rm -v $(pwd):/workdir dusk-dolphin /bin/bash -c "./scripts/build-wii.sh"
|
docker run --rm -v "$(pwd):/workdir" dusk-dolphin /bin/bash -c "./scripts/build-wii.sh"
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
docker build -t dusk-dolphin -f docker/dolphin/Dockerfile .
|
docker build -t dusk-dolphin -f docker/dolphin/Dockerfile .
|
||||||
docker run --rm -v $(pwd):/workdir dusk-dolphin /bin/bash -c "./scripts/build-wii-iso.sh"
|
docker run --rm -v "$(pwd):/workdir" dusk-dolphin /bin/bash -c "./scripts/build-wii-iso.sh"
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ if [ -z "$DEVKITPRO" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
mkdir -p build-wii
|
mkdir -p build-wii
|
||||||
cmake -S. -Bbuild-wii -DDUSK_TARGET_SYSTEM=wii -DCMAKE_TOOLCHAIN_FILE="$DEVKITPRO/cmake/Wii.cmake"
|
cmake -S. -Bbuild-wii \
|
||||||
|
-DDUSK_TARGET_SYSTEM=wii \
|
||||||
|
-DCMAKE_TOOLCHAIN_FILE="$DEVKITPRO/cmake/Wii.cmake" \
|
||||||
|
-DDUSK_DOLPHIN_BUILD_TYPE=DOL
|
||||||
cd build-wii
|
cd build-wii
|
||||||
make -j$(nproc) VERBOSE=1
|
make -j$(nproc) VERBOSE=1
|
||||||
|
mv Dusk.dol boot.dol
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
docker build -t dusk-linux -f docker/linux/Dockerfile .
|
docker build -t dusk-linux -f docker/linux/Dockerfile .
|
||||||
docker run --rm -v $(pwd):/workdir dusk-linux /bin/bash -c "./scripts/test-linux.sh"
|
docker run --rm -v "$(pwd):/workdir" dusk-linux /bin/bash -c "./scripts/test-linux.sh"
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
#include "easing.h"
|
#include "easing.h"
|
||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
#include <math.h>
|
#include "util/math.h"
|
||||||
|
|
||||||
const easingfn_t EASING_FUNCTIONS[EASING_COUNT] = {
|
const easingfn_t EASING_FUNCTIONS[EASING_COUNT] = {
|
||||||
easingLinear,
|
easingLinear,
|
||||||
@@ -36,15 +36,15 @@ float_t easingLinear(const float_t t) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
float_t easingInSine(const float_t t) {
|
float_t easingInSine(const float_t t) {
|
||||||
return 1.0f - cosf(t * EASING_PI * 0.5f);
|
return 1.0f - cosf(t * MATH_PI * 0.5f);
|
||||||
}
|
}
|
||||||
|
|
||||||
float_t easingOutSine(const float_t t) {
|
float_t easingOutSine(const float_t t) {
|
||||||
return sinf(t * EASING_PI * 0.5f);
|
return sinf(t * MATH_PI * 0.5f);
|
||||||
}
|
}
|
||||||
|
|
||||||
float_t easingInOutSine(const float_t t) {
|
float_t easingInOutSine(const float_t t) {
|
||||||
return -(cosf(EASING_PI * t) - 1.0f) * 0.5f;
|
return -(cosf(MATH_PI * t) - 1.0f) * 0.5f;
|
||||||
}
|
}
|
||||||
|
|
||||||
float_t easingInQuad(const float_t t) {
|
float_t easingInQuad(const float_t t) {
|
||||||
|
|||||||
@@ -10,7 +10,17 @@
|
|||||||
#include "util/string.h"
|
#include "util/string.h"
|
||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
|
|
||||||
|
#ifdef DUSK_THREAD_PTHREAD
|
||||||
|
pthread_t ASSERT_MAIN_THREAD_ID = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef DUSK_ASSERTIONS_FAKED
|
#ifndef DUSK_ASSERTIONS_FAKED
|
||||||
|
void assertInit(void) {
|
||||||
|
#ifdef DUSK_THREAD_PTHREAD
|
||||||
|
ASSERT_MAIN_THREAD_ID = pthread_self();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef DUSK_TEST_ASSERT
|
#ifdef DUSK_TEST_ASSERT
|
||||||
void assertTrueImpl(
|
void assertTrueImpl(
|
||||||
const char *file,
|
const char *file,
|
||||||
@@ -132,4 +142,29 @@
|
|||||||
) {
|
) {
|
||||||
assertTrueImpl(file, line, stringCompare(a, b) == 0, message);
|
assertTrueImpl(file, line, stringCompare(a, b) == 0, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void assertIsMainThreadImpl(
|
||||||
|
const char *file,
|
||||||
|
const int32_t line,
|
||||||
|
const char *message
|
||||||
|
) {
|
||||||
|
#ifdef DUSK_THREAD_PTHREAD
|
||||||
|
assertTrueImpl(
|
||||||
|
file, line, pthread_self() == ASSERT_MAIN_THREAD_ID, message
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void assertNotMainThreadImpl(
|
||||||
|
const char *file,
|
||||||
|
const int32_t line,
|
||||||
|
const char *message
|
||||||
|
) {
|
||||||
|
#ifdef DUSK_THREAD_PTHREAD
|
||||||
|
assertTrueImpl(
|
||||||
|
file, line, pthread_self() != ASSERT_MAIN_THREAD_ID, message
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,18 @@
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef DUSK_THREAD_PTHREAD
|
||||||
|
#include "thread/thread.h"
|
||||||
|
extern pthread_t ASSERT_MAIN_THREAD_ID;
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef DUSK_ASSERTIONS_FAKED
|
#ifndef DUSK_ASSERTIONS_FAKED
|
||||||
|
/**
|
||||||
|
* Initializes the assert system. Must be the very first call in engine
|
||||||
|
* startup.
|
||||||
|
*/
|
||||||
|
void assertInit(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assert a given value to be true.
|
* Assert a given value to be true.
|
||||||
*
|
*
|
||||||
@@ -121,6 +132,28 @@
|
|||||||
const char *message
|
const char *message
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the current thread is the main thread.
|
||||||
|
*
|
||||||
|
* @param message Message to throw against assertion failure.
|
||||||
|
*/
|
||||||
|
void assertIsMainThreadImpl(
|
||||||
|
const char *file,
|
||||||
|
const int32_t line,
|
||||||
|
const char *message
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the current thread is NOT the main thread.
|
||||||
|
*
|
||||||
|
* @param message Message to throw against assertion failure.
|
||||||
|
*/
|
||||||
|
void assertNotMainThreadImpl(
|
||||||
|
const char *file,
|
||||||
|
const int32_t line,
|
||||||
|
const char *message
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asserts a given value to be true.
|
* Asserts a given value to be true.
|
||||||
*
|
*
|
||||||
@@ -205,8 +238,28 @@
|
|||||||
#define assertStringEqual(a, b, message) \
|
#define assertStringEqual(a, b, message) \
|
||||||
assertStringEqualImpl(__FILE__, __LINE__, a, b, message)
|
assertStringEqualImpl(__FILE__, __LINE__, a, b, message)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the current thread is the main thread.
|
||||||
|
*
|
||||||
|
* @param message Message to throw against assertion failure.
|
||||||
|
*/
|
||||||
|
#define assertIsMainThread(message) \
|
||||||
|
assertIsMainThreadImpl(__FILE__, __LINE__, message)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the current thread is NOT the main thread.
|
||||||
|
*
|
||||||
|
* @param message Message to throw against assertion failure.
|
||||||
|
*/
|
||||||
|
#define assertNotMainThread(message) \
|
||||||
|
assertNotMainThreadImpl(__FILE__, __LINE__, message)
|
||||||
|
|
||||||
#else
|
#else
|
||||||
// If assertions are faked, we define the macros to do nothing.
|
// If assertions are faked, we define the macros to do nothing.
|
||||||
|
#define assertInit() ((void)0)
|
||||||
|
#define assertMainThreadInit() ((void)0)
|
||||||
|
#define assertIsMainThread(message) ((void)0)
|
||||||
|
#define assertNotMainThread(message) ((void)0)
|
||||||
#define assertTrue(x, message) ((void)0)
|
#define assertTrue(x, message) ((void)0)
|
||||||
#define assertFalse(x, message) ((void)0)
|
#define assertFalse(x, message) ((void)0)
|
||||||
#define assertUnreachable(message) ((void)0)
|
#define assertUnreachable(message) ((void)0)
|
||||||
@@ -215,11 +268,13 @@
|
|||||||
#define assertDeprecated(message) ((void)0)
|
#define assertDeprecated(message) ((void)0)
|
||||||
#define assertStrLenMax(str, len, message) ((void)0)
|
#define assertStrLenMax(str, len, message) ((void)0)
|
||||||
#define assertStrLenMin(str, len, message) ((void)0)
|
#define assertStrLenMin(str, len, message) ((void)0)
|
||||||
|
#define assertStringEqual(a, b, message) ((void)0)
|
||||||
|
#define assertIsMainThread(message) ((void)0)
|
||||||
|
#define assertNotMainThread(message) ((void)0)
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Static Assertions
|
// Static Assertions
|
||||||
|
|
||||||
#define assertStructSize(struct, size) \
|
#define assertStructSize(struct, size) \
|
||||||
_Static_assert(sizeof(struct) == size, "Size of " #struct " must be " #size)
|
_Static_assert(sizeof(struct) == size, "Size of " #struct " must be " #size)
|
||||||
|
|
||||||
|
|||||||
+113
-16
@@ -78,21 +78,83 @@ assetentry_t * assetGetEntry(
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t assetGetEntriesOfType(
|
||||||
|
assetentry_t **outEntries,
|
||||||
|
const assetloadertype_t type
|
||||||
|
) {
|
||||||
|
assertNotNull(outEntries, "Output entries cannot be NULL.");
|
||||||
|
|
||||||
|
uint32_t count = 0;
|
||||||
|
assetentry_t *entry = ASSET.entries;
|
||||||
|
do {
|
||||||
|
if(entry->type == type) {
|
||||||
|
outEntries[count++] = entry;
|
||||||
|
}
|
||||||
|
entry++;
|
||||||
|
} while(entry < ASSET.entries + ASSET_ENTRY_COUNT_MAX);
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
errorret_t assetRequireLoaded(assetentry_t *entry) {
|
errorret_t assetRequireLoaded(assetentry_t *entry) {
|
||||||
assertNotNull(entry, "Entry cannot be NULL.");
|
assertNotNull(entry, "Entry cannot be NULL.");
|
||||||
assertTrue(entry->type != ASSET_LOADER_TYPE_NULL, "Invalid loader type.");
|
assertTrue(entry->type != ASSET_LOADER_TYPE_NULL, "Invalid loader type.");
|
||||||
|
assertIsMainThread("Currently only works on main thread.");
|
||||||
|
|
||||||
// Already loaded?
|
|
||||||
if(entry->state == ASSET_ENTRY_STATE_LOADED) {
|
if(entry->state == ASSET_ENTRY_STATE_LOADED) {
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not loaded, just spin the wheel
|
// Lock to prevent the reaper from collecting the entry mid-spin.
|
||||||
|
assetEntryLock(entry);
|
||||||
|
|
||||||
while(entry->state != ASSET_ENTRY_STATE_LOADED) {
|
while(entry->state != ASSET_ENTRY_STATE_LOADED) {
|
||||||
usleep(1000);
|
usleep(1000);
|
||||||
errorChain(assetUpdate());
|
errorret_t ret = assetUpdate();
|
||||||
|
if(errorIsNotOk(ret)) {
|
||||||
|
assetEntryUnlock(entry);
|
||||||
|
errorChain(ret);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assetEntryUnlock(entry);
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t assetRequireDisposed(assetentry_t *entry) {
|
||||||
|
assertNotNull(entry, "Entry cannot be NULL.");
|
||||||
|
assertTrue(entry->type != ASSET_LOADER_TYPE_NULL, "Invalid loader type.");
|
||||||
|
assertIsMainThread("Currently only works on main thread.");
|
||||||
|
|
||||||
|
if(entry->type == ASSET_LOADER_TYPE_NULL) {
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(
|
||||||
|
entry->state == ASSET_ENTRY_STATE_NOT_STARTED ||
|
||||||
|
entry->state == ASSET_ENTRY_STATE_ERROR
|
||||||
|
) {
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue(
|
||||||
|
entry->refs.count == 0,
|
||||||
|
"Cannot require disposal of an entry with active references."
|
||||||
|
);
|
||||||
|
|
||||||
|
// Lock to prevent the reaper from collecting the entry mid-spin.
|
||||||
|
assetEntryLock(entry);
|
||||||
|
|
||||||
|
while(entry->type != ASSET_LOADER_TYPE_NULL) {
|
||||||
|
usleep(1000);
|
||||||
|
errorret_t ret = assetUpdate();
|
||||||
|
if(errorIsNotOk(ret)) {
|
||||||
|
assetEntryUnlock(entry);
|
||||||
|
errorChain(ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assetEntryUnlock(entry);
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,6 +170,7 @@ assetentry_t * assetLock(
|
|||||||
|
|
||||||
void assetUnlock(const char_t *name) {
|
void assetUnlock(const char_t *name) {
|
||||||
assertNotNull(name, "Name cannot be NULL.");
|
assertNotNull(name, "Name cannot be NULL.");
|
||||||
|
|
||||||
assetentry_t *entry = ASSET.entries;
|
assetentry_t *entry = ASSET.entries;
|
||||||
do {
|
do {
|
||||||
if(entry->type != ASSET_LOADER_TYPE_NULL && stringEquals(entry->name, name)) {
|
if(entry->type != ASSET_LOADER_TYPE_NULL && stringEquals(entry->name, name)) {
|
||||||
@@ -116,6 +179,7 @@ void assetUnlock(const char_t *name) {
|
|||||||
}
|
}
|
||||||
entry++;
|
entry++;
|
||||||
} while(entry < ASSET.entries + ASSET_ENTRY_COUNT_MAX);
|
} while(entry < ASSET.entries + ASSET_ENTRY_COUNT_MAX);
|
||||||
|
|
||||||
assertUnreachable("Asset entry not found for unlock.");
|
assertUnreachable("Asset entry not found for unlock.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,6 +189,8 @@ void assetUnlockEntry(assetentry_t *entry) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
errorret_t assetUpdate(void) {
|
errorret_t assetUpdate(void) {
|
||||||
|
assertIsMainThread("assetUpdate must be called from the main thread.");
|
||||||
|
|
||||||
// Determine how many available loading slots we have.
|
// Determine how many available loading slots we have.
|
||||||
assetloading_t *availableLoading[ASSET_LOADING_COUNT_MAX];
|
assetloading_t *availableLoading[ASSET_LOADING_COUNT_MAX];
|
||||||
uint8_t availableLoadingCount = 0;
|
uint8_t availableLoadingCount = 0;
|
||||||
@@ -191,8 +257,12 @@ errorret_t assetUpdate(void) {
|
|||||||
switch(loading->entry->state) {
|
switch(loading->entry->state) {
|
||||||
// This thing is pending synchronous loading.
|
// This thing is pending synchronous loading.
|
||||||
case ASSET_ENTRY_STATE_PENDING_SYNC:
|
case ASSET_ENTRY_STATE_PENDING_SYNC:
|
||||||
// Perform sync load.
|
|
||||||
loading->entry->state = ASSET_ENTRY_STATE_LOADING_SYNC;
|
loading->entry->state = ASSET_ENTRY_STATE_LOADING_SYNC;
|
||||||
|
// Unlock before calling loadSync. The sync loader may re-enter
|
||||||
|
// assetUpdate (e.g. a script loading another asset), and the async
|
||||||
|
// thread never touches LOADING_SYNC entries, so this is safe.
|
||||||
|
threadMutexUnlock(&loading->mutex);
|
||||||
|
|
||||||
errorret_t ret = (
|
errorret_t ret = (
|
||||||
ASSET_LOADER_CALLBACKS[loading->type].loadSync(loading)
|
ASSET_LOADER_CALLBACKS[loading->type].loadSync(loading)
|
||||||
);
|
);
|
||||||
@@ -206,25 +276,25 @@ errorret_t assetUpdate(void) {
|
|||||||
"Loader did not set entry state to loaded or error on finished load."
|
"Loader did not set entry state to loaded or error on finished load."
|
||||||
);
|
);
|
||||||
|
|
||||||
// If an error occured these things need to be true, basically just
|
|
||||||
// ensuring the sync loader is setting the error correctly.
|
|
||||||
if(errorIsNotOk(ret)) {
|
if(errorIsNotOk(ret)) {
|
||||||
errorCatch(errorPrint(ret));
|
errorCatch(errorPrint(ret));
|
||||||
assertTrue(
|
assertTrue(
|
||||||
loading->entry->state == ASSET_ENTRY_STATE_ERROR,
|
loading->entry->state == ASSET_ENTRY_STATE_ERROR,
|
||||||
"Loader did not set entry state to error on failed load."
|
"Loader did not set entry state to error on failed load."
|
||||||
);
|
);
|
||||||
|
} else if(loading->entry->state == ASSET_ENTRY_STATE_LOADED) {
|
||||||
|
eventInvoke(&loading->entry->onLoaded, loading->entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
threadMutexUnlock(&loading->mutex);
|
|
||||||
loading++;
|
loading++;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ASSET_ENTRY_STATE_LOADING_SYNC:
|
case ASSET_ENTRY_STATE_LOADING_SYNC:
|
||||||
assertUnreachable(
|
// A re-entrant assetUpdate call (e.g. from a script loading another
|
||||||
"Entry is in a pending sync state still?"
|
// asset) will see this entry mid-sync-load. Skip it.
|
||||||
);
|
threadMutexUnlock(&loading->mutex);
|
||||||
break;
|
loading++;
|
||||||
|
continue;
|
||||||
|
|
||||||
// Done loading, we can just free it up.
|
// Done loading, we can just free it up.
|
||||||
case ASSET_ENTRY_STATE_LOADED:
|
case ASSET_ENTRY_STATE_LOADED:
|
||||||
@@ -233,10 +303,14 @@ errorret_t assetUpdate(void) {
|
|||||||
loading++;
|
loading++;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ASSET_ENTRY_STATE_ERROR:
|
case ASSET_ENTRY_STATE_ERROR: {
|
||||||
|
assetentry_t *errEntry = loading->entry;
|
||||||
|
loading->entry = NULL;
|
||||||
threadMutexUnlock(&loading->mutex);
|
threadMutexUnlock(&loading->mutex);
|
||||||
|
eventInvoke(&errEntry->onError, errEntry);
|
||||||
errorThrow("Failed to load asset asynchronously.");
|
errorThrow("Failed to load asset asynchronously.");
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
threadMutexUnlock(&loading->mutex);
|
threadMutexUnlock(&loading->mutex);
|
||||||
@@ -246,9 +320,7 @@ errorret_t assetUpdate(void) {
|
|||||||
} while(loading < ASSET.loading + ASSET_LOADING_COUNT_MAX);
|
} while(loading < ASSET.loading + ASSET_LOADING_COUNT_MAX);
|
||||||
|
|
||||||
|
|
||||||
// Reap entries that have no external locks (refs.count == 1 means only the
|
// Reap unused entries.
|
||||||
// system hold remains). Only safe to reap LOADED and NOT_STARTED states —
|
|
||||||
// mid-load entries are left for the next cycle.
|
|
||||||
entry = ASSET.entries;
|
entry = ASSET.entries;
|
||||||
do {
|
do {
|
||||||
if(entry->state != ASSET_ENTRY_STATE_LOADED) {
|
if(entry->state != ASSET_ENTRY_STATE_LOADED) {
|
||||||
@@ -275,6 +347,8 @@ errorret_t assetUpdate(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void assetUpdateAsync(thread_t *thread) {
|
void assetUpdateAsync(thread_t *thread) {
|
||||||
|
assertNotMainThread("assetUpdateAsync must not run on the main thread.");
|
||||||
|
|
||||||
while(!threadShouldStop(thread)) {
|
while(!threadShouldStop(thread)) {
|
||||||
// Walk over each asset
|
// Walk over each asset
|
||||||
assetloading_t *loading;
|
assetloading_t *loading;
|
||||||
@@ -331,11 +405,34 @@ void assetUpdateAsync(thread_t *thread) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
errorret_t assetDispose(void) {
|
errorret_t assetDispose(void) {
|
||||||
|
assertIsMainThread("Must be called from the main thread.");
|
||||||
threadStop(&ASSET.loadThread);
|
threadStop(&ASSET.loadThread);
|
||||||
|
|
||||||
|
// Free any script read-buffers left behind by an in-flight async load
|
||||||
|
// that was interrupted before the sync eval phase ran.
|
||||||
for(size_t i = 0; i < ASSET_LOADING_COUNT_MAX; i++) {
|
for(size_t i = 0; i < ASSET_LOADING_COUNT_MAX; i++) {
|
||||||
threadMutexDispose(&ASSET.loading[i].mutex);
|
assetloading_t *loading = &ASSET.loading[i];
|
||||||
|
if(
|
||||||
|
loading->entry != NULL &&
|
||||||
|
loading->type == ASSET_LOADER_TYPE_SCRIPT &&
|
||||||
|
loading->loading.script.buffer != NULL
|
||||||
|
) {
|
||||||
|
memoryFree(loading->loading.script.buffer);
|
||||||
|
loading->loading.script.buffer = NULL;
|
||||||
}
|
}
|
||||||
|
threadMutexDispose(&loading->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dispose every non-null entry so type-specific dispose callbacks
|
||||||
|
// (e.g. assetScriptDispose freeing jerry values) run before the
|
||||||
|
// scripting engine is torn down.
|
||||||
|
assetentry_t *entry = ASSET.entries;
|
||||||
|
do {
|
||||||
|
if(entry->type != ASSET_LOADER_TYPE_NULL) {
|
||||||
|
errorChain(assetEntryDispose(entry));
|
||||||
|
}
|
||||||
|
entry++;
|
||||||
|
} while(entry < ASSET.entries + ASSET_ENTRY_COUNT_MAX);
|
||||||
|
|
||||||
// Cleanup zip file.
|
// Cleanup zip file.
|
||||||
if(ASSET.zip != NULL) {
|
if(ASSET.zip != NULL) {
|
||||||
|
|||||||
@@ -68,6 +68,18 @@ assetentry_t * assetGetEntry(
|
|||||||
assetloaderinput_t *input
|
assetloaderinput_t *input
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all asset entries of a given type.
|
||||||
|
*
|
||||||
|
* @param outEntries Output array to write the entries to.
|
||||||
|
* @param type Type of the asset entries to get.
|
||||||
|
* @return The number of entries written to outEntries.
|
||||||
|
*/
|
||||||
|
uint32_t assetGetEntriesOfType(
|
||||||
|
assetentry_t **outEntries,
|
||||||
|
const assetloadertype_t type
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets, creates, and locks an asset entry. The asset will begin loading on
|
* Gets, creates, and locks an asset entry. The asset will begin loading on
|
||||||
* the next assetUpdate. Call assetUnlock when done to allow the entry to be
|
* the next assetUpdate. Call assetUnlock when done to allow the entry to be
|
||||||
@@ -109,6 +121,15 @@ void assetUnlockEntry(assetentry_t *entry);
|
|||||||
*/
|
*/
|
||||||
errorret_t assetRequireLoaded(assetentry_t *entry);
|
errorret_t assetRequireLoaded(assetentry_t *entry);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requires an asset entry to be disposed. This will block until the asset entry
|
||||||
|
* is fully disposed.
|
||||||
|
*
|
||||||
|
* @param entry The asset entry to require disposal of.
|
||||||
|
* @return An error code if the asset entry could not be disposed properly.
|
||||||
|
*/
|
||||||
|
errorret_t assetRequireDisposed(assetentry_t *entry);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the asset system.
|
* Updates the asset system.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -11,6 +11,38 @@
|
|||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
/* ---- Per-entry event trampolines ----------------------------------------- */
|
||||||
|
|
||||||
|
static void assetBatchEntryOnLoadedCb(void *params, void *user) {
|
||||||
|
assetentry_t *entry = (assetentry_t *)params;
|
||||||
|
assetbatch_t *batch = (assetbatch_t *)user;
|
||||||
|
|
||||||
|
batch->loadedCount++;
|
||||||
|
eventInvoke(&batch->onEntryLoaded, entry);
|
||||||
|
|
||||||
|
if((uint16_t)(batch->loadedCount + batch->errorCount) >= batch->count) {
|
||||||
|
if(batch->errorCount == 0) {
|
||||||
|
eventInvoke(&batch->onLoaded, batch);
|
||||||
|
} else {
|
||||||
|
eventInvoke(&batch->onError, batch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void assetBatchEntryOnErrorCb(void *params, void *user) {
|
||||||
|
assetentry_t *entry = (assetentry_t *)params;
|
||||||
|
assetbatch_t *batch = (assetbatch_t *)user;
|
||||||
|
|
||||||
|
batch->errorCount++;
|
||||||
|
eventInvoke(&batch->onEntryError, entry);
|
||||||
|
|
||||||
|
if((uint16_t)(batch->loadedCount + batch->errorCount) >= batch->count) {
|
||||||
|
eventInvoke(&batch->onError, batch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Public API ---------------------------------------------------------- */
|
||||||
|
|
||||||
void assetBatchInit(
|
void assetBatchInit(
|
||||||
assetbatch_t *batch,
|
assetbatch_t *batch,
|
||||||
const uint16_t count,
|
const uint16_t count,
|
||||||
@@ -24,10 +56,40 @@ void assetBatchInit(
|
|||||||
memoryZero(batch, sizeof(assetbatch_t));
|
memoryZero(batch, sizeof(assetbatch_t));
|
||||||
batch->count = count;
|
batch->count = count;
|
||||||
|
|
||||||
|
eventInit(
|
||||||
|
&batch->onLoaded,
|
||||||
|
batch->onLoadedCallbacks, batch->onLoadedUsers, ASSET_BATCH_EVENT_MAX
|
||||||
|
);
|
||||||
|
eventInit(
|
||||||
|
&batch->onEntryLoaded,
|
||||||
|
batch->onEntryLoadedCallbacks, batch->onEntryLoadedUsers, ASSET_BATCH_EVENT_MAX
|
||||||
|
);
|
||||||
|
eventInit(
|
||||||
|
&batch->onError,
|
||||||
|
batch->onErrorCallbacks, batch->onErrorUsers, ASSET_BATCH_EVENT_MAX
|
||||||
|
);
|
||||||
|
eventInit(
|
||||||
|
&batch->onEntryError,
|
||||||
|
batch->onEntryErrorCallbacks, batch->onEntryErrorUsers, ASSET_BATCH_EVENT_MAX
|
||||||
|
);
|
||||||
|
|
||||||
for(uint16_t i = 0; i < count; i++) {
|
for(uint16_t i = 0; i < count; i++) {
|
||||||
// Copy input into batch-owned storage so the descriptor need not persist.
|
|
||||||
batch->inputs[i] = descs[i].input;
|
batch->inputs[i] = descs[i].input;
|
||||||
batch->entries[i] = assetLock(descs[i].path, descs[i].type, &batch->inputs[i]);
|
batch->entries[i] = assetLock(descs[i].path, descs[i].type, &batch->inputs[i]);
|
||||||
|
|
||||||
|
if(batch->entries[i]->state == ASSET_ENTRY_STATE_LOADED) {
|
||||||
|
/* Already loaded (cached) — count it now, no subscription needed. */
|
||||||
|
batch->loadedCount++;
|
||||||
|
} else if(batch->entries[i]->state == ASSET_ENTRY_STATE_ERROR) {
|
||||||
|
batch->errorCount++;
|
||||||
|
} else {
|
||||||
|
eventSubscribe(
|
||||||
|
&batch->entries[i]->onLoaded, assetBatchEntryOnLoadedCb, batch
|
||||||
|
);
|
||||||
|
eventSubscribe(
|
||||||
|
&batch->entries[i]->onError, assetBatchEntryOnErrorCb, batch
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +150,12 @@ errorret_t assetBatchRequireLoaded(assetbatch_t *batch) {
|
|||||||
void assetBatchDispose(assetbatch_t *batch) {
|
void assetBatchDispose(assetbatch_t *batch) {
|
||||||
assertNotNull(batch, "Batch cannot be NULL.");
|
assertNotNull(batch, "Batch cannot be NULL.");
|
||||||
for(uint16_t i = 0; i < batch->count; i++) {
|
for(uint16_t i = 0; i < batch->count; i++) {
|
||||||
|
if(batch->entries[i]) {
|
||||||
|
/* Unsubscribe while we still hold a lock so the entry is guaranteed live. */
|
||||||
|
eventUnsubscribe(&batch->entries[i]->onLoaded, assetBatchEntryOnLoadedCb);
|
||||||
|
eventUnsubscribe(&batch->entries[i]->onError, assetBatchEntryOnErrorCb);
|
||||||
assetUnlockEntry(batch->entries[i]);
|
assetUnlockEntry(batch->entries[i]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
memoryZero(batch, sizeof(assetbatch_t));
|
memoryZero(batch, sizeof(assetbatch_t));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "asset/loader/assetentry.h"
|
#include "asset/loader/assetentry.h"
|
||||||
#include "asset/loader/assetloader.h"
|
#include "asset/loader/assetloader.h"
|
||||||
|
#include "event/event.h"
|
||||||
|
|
||||||
#define ASSET_BATCH_COUNT_MAX 64
|
#define ASSET_BATCH_COUNT_MAX 64
|
||||||
|
#define ASSET_BATCH_EVENT_MAX 4
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const char_t *path;
|
const char_t *path;
|
||||||
@@ -21,6 +23,28 @@ typedef struct {
|
|||||||
assetentry_t *entries[ASSET_BATCH_COUNT_MAX];
|
assetentry_t *entries[ASSET_BATCH_COUNT_MAX];
|
||||||
assetloaderinput_t inputs[ASSET_BATCH_COUNT_MAX];
|
assetloaderinput_t inputs[ASSET_BATCH_COUNT_MAX];
|
||||||
uint16_t count;
|
uint16_t count;
|
||||||
|
uint16_t loadedCount;
|
||||||
|
uint16_t errorCount;
|
||||||
|
|
||||||
|
/** Fires once when every entry has loaded successfully. params = assetbatch_t * */
|
||||||
|
event_t onLoaded;
|
||||||
|
eventcallback_t onLoadedCallbacks[ASSET_BATCH_EVENT_MAX];
|
||||||
|
void *onLoadedUsers[ASSET_BATCH_EVENT_MAX];
|
||||||
|
|
||||||
|
/** Fires each time a single entry finishes loading. params = assetentry_t * */
|
||||||
|
event_t onEntryLoaded;
|
||||||
|
eventcallback_t onEntryLoadedCallbacks[ASSET_BATCH_EVENT_MAX];
|
||||||
|
void *onEntryLoadedUsers[ASSET_BATCH_EVENT_MAX];
|
||||||
|
|
||||||
|
/** Fires once when all entries have finished (any with errors). params = assetbatch_t * */
|
||||||
|
event_t onError;
|
||||||
|
eventcallback_t onErrorCallbacks[ASSET_BATCH_EVENT_MAX];
|
||||||
|
void *onErrorUsers[ASSET_BATCH_EVENT_MAX];
|
||||||
|
|
||||||
|
/** Fires each time a single entry errors. params = assetentry_t * */
|
||||||
|
event_t onEntryError;
|
||||||
|
eventcallback_t onEntryErrorCallbacks[ASSET_BATCH_EVENT_MAX];
|
||||||
|
void *onEntryErrorUsers[ASSET_BATCH_EVENT_MAX];
|
||||||
} assetbatch_t;
|
} assetbatch_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -117,6 +117,48 @@ errorret_t assetFileDispose(assetfile_t *file) {
|
|||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
errorret_t assetFileReadEntire(
|
||||||
|
assetfile_t *file,
|
||||||
|
uint8_t **outBuffer,
|
||||||
|
size_t *outSize
|
||||||
|
) {
|
||||||
|
assertNotNull(file, "Asset file cannot be NULL.");
|
||||||
|
assertNotNull(outBuffer, "outBuffer cannot be NULL.");
|
||||||
|
assertNotNull(outSize, "outSize cannot be NULL.");
|
||||||
|
assertTrue(file->size > 0, "Asset file has no size; call assetFileInit first.");
|
||||||
|
|
||||||
|
// File should be closed currently.
|
||||||
|
assertNull(file->zipFile, "Asset file must be closed before reading entire.");
|
||||||
|
|
||||||
|
// Open file
|
||||||
|
errorret_t ret = assetFileOpen(file);
|
||||||
|
if(errorIsNotOk(ret)) {
|
||||||
|
errorChain(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set output.
|
||||||
|
size_t size = (size_t)file->size;
|
||||||
|
uint8_t *buffer = (uint8_t *)memoryAllocate(size);
|
||||||
|
|
||||||
|
// Read entire file.
|
||||||
|
ret = assetFileRead(file, buffer, size);
|
||||||
|
if(errorIsNotOk(ret)) {
|
||||||
|
memoryFree(buffer);
|
||||||
|
errorChain(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the file.
|
||||||
|
ret = assetFileClose(file);
|
||||||
|
if(errorIsNotOk(ret)) {
|
||||||
|
memoryFree(buffer);
|
||||||
|
errorChain(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
*outBuffer = buffer;
|
||||||
|
*outSize = size;
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
// Line Reader;
|
// Line Reader;
|
||||||
void assetFileLineReaderInit(
|
void assetFileLineReaderInit(
|
||||||
assetfilelinereader_t *reader,
|
assetfilelinereader_t *reader,
|
||||||
|
|||||||
@@ -92,6 +92,21 @@ errorret_t assetFileClose(assetfile_t *file);
|
|||||||
*/
|
*/
|
||||||
errorret_t assetFileDispose(assetfile_t *file);
|
errorret_t assetFileDispose(assetfile_t *file);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the entire contents of the asset file into a newly allocated buffer.
|
||||||
|
* The caller is responsible for freeing the buffer with memoryFree.
|
||||||
|
*
|
||||||
|
* @param file The asset file to read. Must be initialized but not open.
|
||||||
|
* @param outBuffer Receives a pointer to the allocated buffer.
|
||||||
|
* @param outSize Receives the number of bytes written to the buffer.
|
||||||
|
* @return An error code if the file could not be read.
|
||||||
|
*/
|
||||||
|
errorret_t assetFileReadEntire(
|
||||||
|
assetfile_t *file,
|
||||||
|
uint8_t **outBuffer,
|
||||||
|
size_t *outSize
|
||||||
|
);
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
assetfile_t *file;
|
assetfile_t *file;
|
||||||
uint8_t *readBuffer;
|
uint8_t *readBuffer;
|
||||||
|
|||||||
@@ -21,24 +21,49 @@ void assetEntryInit(
|
|||||||
assertStrLenMax(name, ASSET_FILE_NAME_MAX - 1, "Name too long");
|
assertStrLenMax(name, ASSET_FILE_NAME_MAX - 1, "Name too long");
|
||||||
assertTrue(type != ASSET_LOADER_TYPE_NULL, "Invalid loader type.");
|
assertTrue(type != ASSET_LOADER_TYPE_NULL, "Invalid loader type.");
|
||||||
assertTrue(type < ASSET_LOADER_TYPE_COUNT, "Invalid loader type.");
|
assertTrue(type < ASSET_LOADER_TYPE_COUNT, "Invalid loader type.");
|
||||||
|
assertIsMainThread("Must be called from the main thread.");
|
||||||
|
|
||||||
memoryZero(entry, sizeof(assetentry_t));
|
memoryZero(entry, sizeof(assetentry_t));
|
||||||
stringCopy(entry->name, name, ASSET_FILE_NAME_MAX);
|
stringCopy(entry->name, name, ASSET_FILE_NAME_MAX);
|
||||||
entry->type = type;
|
entry->type = type;
|
||||||
entry->input = input;
|
|
||||||
entry->state = ASSET_ENTRY_STATE_NOT_STARTED;
|
entry->state = ASSET_ENTRY_STATE_NOT_STARTED;
|
||||||
|
if(input) {
|
||||||
|
entry->inputData = *input;
|
||||||
|
entry->input = &entry->inputData;
|
||||||
|
} else {
|
||||||
|
memoryZero(&entry->inputData, sizeof(assetloaderinput_t));
|
||||||
|
entry->input = NULL;
|
||||||
|
}
|
||||||
refInit(&entry->refs, entry, NULL, NULL, NULL);
|
refInit(&entry->refs, entry, NULL, NULL, NULL);
|
||||||
|
|
||||||
|
eventInit(
|
||||||
|
&entry->onLoaded,
|
||||||
|
entry->onLoadedCallbacks, entry->onLoadedUsers,
|
||||||
|
ASSET_ENTRY_EVENT_MAX
|
||||||
|
);
|
||||||
|
eventInit(
|
||||||
|
&entry->onUnloaded,
|
||||||
|
entry->onUnloadedCallbacks, entry->onUnloadedUsers,
|
||||||
|
ASSET_ENTRY_EVENT_MAX
|
||||||
|
);
|
||||||
|
eventInit(
|
||||||
|
&entry->onError,
|
||||||
|
entry->onErrorCallbacks, entry->onErrorUsers,
|
||||||
|
ASSET_ENTRY_EVENT_MAX
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void assetEntryLock(assetentry_t *entry) {
|
void assetEntryLock(assetentry_t *entry) {
|
||||||
assertNotNull(entry, "Entry cannot be NULL");
|
assertNotNull(entry, "Entry cannot be NULL");
|
||||||
assertTrue(entry->type != ASSET_LOADER_TYPE_NULL, "Invalid loader type.");
|
assertTrue(entry->type != ASSET_LOADER_TYPE_NULL, "Invalid loader type.");
|
||||||
|
|
||||||
refLock(&entry->refs);
|
refLock(&entry->refs);
|
||||||
}
|
}
|
||||||
|
|
||||||
void assetEntryUnlock(assetentry_t *entry) {
|
void assetEntryUnlock(assetentry_t *entry) {
|
||||||
assertNotNull(entry, "Entry cannot be NULL");
|
assertNotNull(entry, "Entry cannot be NULL");
|
||||||
assertTrue(entry->type != ASSET_LOADER_TYPE_NULL, "Invalid loader type.");
|
assertTrue(entry->type != ASSET_LOADER_TYPE_NULL, "Invalid loader type.");
|
||||||
|
|
||||||
refUnlock(&entry->refs);
|
refUnlock(&entry->refs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,6 +78,7 @@ void assetEntryStartLoading(
|
|||||||
entry->state == ASSET_ENTRY_STATE_NOT_STARTED,
|
entry->state == ASSET_ENTRY_STATE_NOT_STARTED,
|
||||||
"Can only start loading from NOT_STARTED state."
|
"Can only start loading from NOT_STARTED state."
|
||||||
);
|
);
|
||||||
|
assertIsMainThread("Must be called from the main thread.");
|
||||||
|
|
||||||
entry->state = ASSET_ENTRY_STATE_PENDING_SYNC;
|
entry->state = ASSET_ENTRY_STATE_PENDING_SYNC;
|
||||||
memoryZero(&loading->loading, sizeof(assetloaderloading_t));
|
memoryZero(&loading->loading, sizeof(assetloaderloading_t));
|
||||||
@@ -63,10 +89,15 @@ void assetEntryStartLoading(
|
|||||||
|
|
||||||
errorret_t assetEntryDispose(assetentry_t *entry) {
|
errorret_t assetEntryDispose(assetentry_t *entry) {
|
||||||
assertNotNull(entry, "Entry cannot be NULL");
|
assertNotNull(entry, "Entry cannot be NULL");
|
||||||
|
|
||||||
assertTrue(entry->type != ASSET_LOADER_TYPE_NULL, "Invalid loader type.");
|
assertTrue(entry->type != ASSET_LOADER_TYPE_NULL, "Invalid loader type.");
|
||||||
assertTrue(entry->type < ASSET_LOADER_TYPE_COUNT, "Invalid loader type.");
|
assertTrue(entry->type < ASSET_LOADER_TYPE_COUNT, "Invalid loader type.");
|
||||||
|
assertIsMainThread("Must be called from the main thread.");
|
||||||
|
assertTrue(
|
||||||
|
entry->refs.count == 0,
|
||||||
|
"Asset entry still refed at dispose time."
|
||||||
|
);
|
||||||
|
|
||||||
|
eventInvoke(&entry->onUnloaded, entry);
|
||||||
errorChain(ASSET_LOADER_CALLBACKS[entry->type].dispose(entry));
|
errorChain(ASSET_LOADER_CALLBACKS[entry->type].dispose(entry));
|
||||||
memoryZero(entry, sizeof(assetentry_t));
|
memoryZero(entry, sizeof(assetentry_t));
|
||||||
errorOk();
|
errorOk();
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "asset/loader/assetloading.h"
|
#include "asset/loader/assetloading.h"
|
||||||
|
#include "event/event.h"
|
||||||
#include "util/ref.h"
|
#include "util/ref.h"
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
@@ -19,20 +20,44 @@ typedef enum {
|
|||||||
ASSET_ENTRY_STATE_ERROR
|
ASSET_ENTRY_STATE_ERROR
|
||||||
} assetentrystate_t;
|
} assetentrystate_t;
|
||||||
|
|
||||||
typedef struct assetentry_s {
|
/** Maximum number of subscribers for each per-entry event. */
|
||||||
// Filename and cache key
|
#define ASSET_ENTRY_EVENT_MAX 2
|
||||||
|
|
||||||
|
typedef struct assetentry_s assetentry_t;
|
||||||
|
|
||||||
|
struct assetentry_s {
|
||||||
char_t name[ASSET_FILE_NAME_MAX];
|
char_t name[ASSET_FILE_NAME_MAX];
|
||||||
// What type of asset is this?
|
|
||||||
assetloadertype_t type;
|
assetloadertype_t type;
|
||||||
// Data
|
|
||||||
assetloaderoutput_t data;
|
assetloaderoutput_t data;
|
||||||
// What state is this asset entry in currently?
|
|
||||||
assetentrystate_t state;
|
assetentrystate_t state;
|
||||||
// What is referencing this asset entry.
|
|
||||||
ref_t refs;
|
ref_t refs;
|
||||||
// Data that will be passed to the loader about how it should load.
|
|
||||||
assetloaderinput_t *input;
|
assetloaderinput_t *input;
|
||||||
} assetentry_t;
|
assetloaderinput_t inputData;
|
||||||
|
/**
|
||||||
|
* Fired once when loading completes successfully (params = assetentry_t *).
|
||||||
|
* Always invoked on the main thread.
|
||||||
|
*/
|
||||||
|
event_t onLoaded;
|
||||||
|
eventcallback_t onLoadedCallbacks[ASSET_ENTRY_EVENT_MAX];
|
||||||
|
void *onLoadedUsers[ASSET_ENTRY_EVENT_MAX];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fired once when the entry is disposed/reaped (params = assetentry_t *).
|
||||||
|
* The asset data is still accessible when the callback runs.
|
||||||
|
* Always invoked on the main thread.
|
||||||
|
*/
|
||||||
|
event_t onUnloaded;
|
||||||
|
eventcallback_t onUnloadedCallbacks[ASSET_ENTRY_EVENT_MAX];
|
||||||
|
void *onUnloadedUsers[ASSET_ENTRY_EVENT_MAX];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fired once when loading fails (params = assetentry_t *).
|
||||||
|
* Always invoked on the main thread.
|
||||||
|
*/
|
||||||
|
event_t onError;
|
||||||
|
eventcallback_t onErrorCallbacks[ASSET_ENTRY_EVENT_MAX];
|
||||||
|
void *onErrorUsers[ASSET_ENTRY_EVENT_MAX];
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes an asset entry with the given name and type. This does not load
|
* Initializes an asset entry with the given name and type. This does not load
|
||||||
@@ -79,6 +104,7 @@ void assetEntryStartLoading(assetentry_t *entry, assetloading_t *loading);
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Disposes an asset entry, freeing any resources it holds.
|
* Disposes an asset entry, freeing any resources it holds.
|
||||||
|
* Fires the onUnloaded event before releasing asset data.
|
||||||
*
|
*
|
||||||
* @param entry The asset entry to dispose.
|
* @param entry The asset entry to dispose.
|
||||||
* @return Any error that occurs during disposal.
|
* @return Any error that occurs during disposal.
|
||||||
|
|||||||
@@ -13,13 +13,9 @@
|
|||||||
typedef struct assetentry_s assetentry_t;
|
typedef struct assetentry_s assetentry_t;
|
||||||
|
|
||||||
typedef struct assetloading_s {
|
typedef struct assetloading_s {
|
||||||
// Protects entry pointer and entry->state from concurrent access.
|
|
||||||
threadmutex_t mutex;
|
threadmutex_t mutex;
|
||||||
// What type of asset is being loaded.
|
|
||||||
assetloadertype_t type;
|
assetloadertype_t type;
|
||||||
// Referral back to the asset entry that will be kept alive after load done.
|
|
||||||
assetentry_t *entry;
|
assetentry_t *entry;
|
||||||
// Information used during the load operation only.
|
|
||||||
assetloaderloading_t loading;
|
assetloaderloading_t loading;
|
||||||
} assetloading_t;
|
} assetloading_t;
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ errorret_t assetMeshLoaderAsync(assetloading_t *loading) {
|
|||||||
|
|
||||||
assetmeshoutput_t *out = &loading->entry->data.mesh;
|
assetmeshoutput_t *out = &loading->entry->data.mesh;
|
||||||
assetfile_t *file = &loading->loading.mesh.file;
|
assetfile_t *file = &loading->loading.mesh.file;
|
||||||
assetmeshinputaxis_t axis = loading->entry->input->mesh;
|
assetmeshinputaxis_t axis = loading->entry->inputData.mesh;
|
||||||
|
|
||||||
assetLoaderErrorChain(loading, assetFileInit(file, loading->entry->name, NULL, NULL));
|
assetLoaderErrorChain(loading, assetFileInit(file, loading->entry->name, NULL, NULL));
|
||||||
assetLoaderErrorChain(loading, assetFileOpen(file));
|
assetLoaderErrorChain(loading, assetFileOpen(file));
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ int assetTextureEOF(void *user) {
|
|||||||
|
|
||||||
errorret_t assetTextureLoaderAsync(assetloading_t *loading) {
|
errorret_t assetTextureLoaderAsync(assetloading_t *loading) {
|
||||||
assertNotNull(loading, "Loading cannot be NULL");
|
assertNotNull(loading, "Loading cannot be NULL");
|
||||||
|
assertNotMainThread("Should be called from an async thread.");
|
||||||
|
|
||||||
// Only care about loading pixels.
|
// Only care about loading pixels.
|
||||||
if(loading->loading.texture.state != ASSET_TEXTURE_LOADING_STATE_LOAD_PIXELS){
|
if(loading->loading.texture.state != ASSET_TEXTURE_LOADING_STATE_LOAD_PIXELS){
|
||||||
@@ -76,7 +77,7 @@ errorret_t assetTextureLoaderAsync(assetloading_t *loading) {
|
|||||||
|
|
||||||
// Determine channels
|
// Determine channels
|
||||||
int channelsDesired;
|
int channelsDesired;
|
||||||
switch(loading->entry->input->texture) {
|
switch(loading->entry->inputData.texture) {
|
||||||
case TEXTURE_FORMAT_RGBA:
|
case TEXTURE_FORMAT_RGBA:
|
||||||
channelsDesired = 4;
|
channelsDesired = 4;
|
||||||
break;
|
break;
|
||||||
@@ -122,6 +123,7 @@ errorret_t assetTextureLoaderAsync(assetloading_t *loading) {
|
|||||||
|
|
||||||
errorret_t assetTextureLoaderSync(assetloading_t *loading) {
|
errorret_t assetTextureLoaderSync(assetloading_t *loading) {
|
||||||
assertNotNull(loading, "Loading cannot be NULL");
|
assertNotNull(loading, "Loading cannot be NULL");
|
||||||
|
assertIsMainThread("Must be called from the main thread.");
|
||||||
|
|
||||||
switch(loading->loading.texture.state) {
|
switch(loading->loading.texture.state) {
|
||||||
case ASSET_TEXTURE_LOADING_STATE_INITIAL:
|
case ASSET_TEXTURE_LOADING_STATE_INITIAL:
|
||||||
@@ -146,7 +148,7 @@ errorret_t assetTextureLoaderSync(assetloading_t *loading) {
|
|||||||
(texture_t*)&loading->entry->data.texture,
|
(texture_t*)&loading->entry->data.texture,
|
||||||
loading->loading.texture.width,
|
loading->loading.texture.width,
|
||||||
loading->loading.texture.height,
|
loading->loading.texture.height,
|
||||||
loading->entry->input->texture,
|
loading->entry->inputData.texture,
|
||||||
(texturedata_t){
|
(texturedata_t){
|
||||||
.rgbaColors = (color_t*)loading->loading.texture.data
|
.rgbaColors = (color_t*)loading->loading.texture.data
|
||||||
}
|
}
|
||||||
@@ -161,5 +163,7 @@ errorret_t assetTextureLoaderSync(assetloading_t *loading) {
|
|||||||
|
|
||||||
errorret_t assetTextureDispose(assetentry_t *entry) {
|
errorret_t assetTextureDispose(assetentry_t *entry) {
|
||||||
assertNotNull(entry, "Asset entry cannot be NULL");
|
assertNotNull(entry, "Asset entry cannot be NULL");
|
||||||
|
assertIsMainThread("Must be called from the main thread.");
|
||||||
|
|
||||||
return textureDispose(&entry->data.texture);
|
return textureDispose(&entry->data.texture);
|
||||||
}
|
}
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
errorret_t assetTilesetLoaderAsync(assetloading_t *loading) {
|
errorret_t assetTilesetLoaderAsync(assetloading_t *loading) {
|
||||||
assertNotNull(loading, "Loading cannot be NULL");
|
assertNotNull(loading, "Loading cannot be NULL");
|
||||||
|
assertNotMainThread("Should be called from an async thread.");
|
||||||
|
|
||||||
if(loading->loading.tileset.state != ASSET_TILESET_LOADING_STATE_READ_FILE) {
|
if(loading->loading.tileset.state != ASSET_TILESET_LOADING_STATE_READ_FILE) {
|
||||||
errorOk();
|
errorOk();
|
||||||
@@ -40,6 +41,7 @@ errorret_t assetTilesetLoaderAsync(assetloading_t *loading) {
|
|||||||
errorret_t assetTilesetLoaderSync(assetloading_t *loading) {
|
errorret_t assetTilesetLoaderSync(assetloading_t *loading) {
|
||||||
assertNotNull(loading, "Loading cannot be NULL");
|
assertNotNull(loading, "Loading cannot be NULL");
|
||||||
assertTrue(loading->type == ASSET_LOADER_TYPE_TILESET, "Invalid type.");
|
assertTrue(loading->type == ASSET_LOADER_TYPE_TILESET, "Invalid type.");
|
||||||
|
assertIsMainThread("Must be called from the main thread.");
|
||||||
|
|
||||||
switch(loading->loading.tileset.state) {
|
switch(loading->loading.tileset.state) {
|
||||||
case ASSET_TILESET_LOADING_STATE_INITIAL:
|
case ASSET_TILESET_LOADING_STATE_INITIAL:
|
||||||
@@ -111,5 +113,7 @@ errorret_t assetTilesetLoaderSync(assetloading_t *loading) {
|
|||||||
errorret_t assetTilesetDispose(assetentry_t *entry) {
|
errorret_t assetTilesetDispose(assetentry_t *entry) {
|
||||||
assertNotNull(entry, "Entry cannot be NULL");
|
assertNotNull(entry, "Entry cannot be NULL");
|
||||||
assertTrue(entry->type == ASSET_LOADER_TYPE_TILESET, "Invalid type.");
|
assertTrue(entry->type == ASSET_LOADER_TYPE_TILESET, "Invalid type.");
|
||||||
|
assertIsMainThread("Must be called from the main thread.");
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
errorret_t assetJsonLoaderAsync(assetloading_t *loading) {
|
errorret_t assetJsonLoaderAsync(assetloading_t *loading) {
|
||||||
assertNotNull(loading, "Loading cannot be NULL");
|
assertNotNull(loading, "Loading cannot be NULL");
|
||||||
|
assertNotMainThread("Async loader should not be on main thread.");
|
||||||
|
|
||||||
if(loading->loading.json.state != ASSET_JSON_LOADING_STATE_READ_FILE) {
|
if(loading->loading.json.state != ASSET_JSON_LOADING_STATE_READ_FILE) {
|
||||||
errorOk();
|
errorOk();
|
||||||
@@ -45,6 +46,7 @@ errorret_t assetJsonLoaderAsync(assetloading_t *loading) {
|
|||||||
errorret_t assetJsonLoaderSync(assetloading_t *loading) {
|
errorret_t assetJsonLoaderSync(assetloading_t *loading) {
|
||||||
assertNotNull(loading, "Loading cannot be NULL");
|
assertNotNull(loading, "Loading cannot be NULL");
|
||||||
assertTrue(loading->type == ASSET_LOADER_TYPE_JSON, "Invalid type.");
|
assertTrue(loading->type == ASSET_LOADER_TYPE_JSON, "Invalid type.");
|
||||||
|
assertIsMainThread("Must be called from the main thread.");
|
||||||
|
|
||||||
switch(loading->loading.json.state) {
|
switch(loading->loading.json.state) {
|
||||||
case ASSET_JSON_LOADING_STATE_INITIAL:
|
case ASSET_JSON_LOADING_STATE_INITIAL:
|
||||||
@@ -82,7 +84,10 @@ errorret_t assetJsonLoaderSync(assetloading_t *loading) {
|
|||||||
errorret_t assetJsonDispose(assetentry_t *entry) {
|
errorret_t assetJsonDispose(assetentry_t *entry) {
|
||||||
assertNotNull(entry, "Asset entry cannot be NULL");
|
assertNotNull(entry, "Asset entry cannot be NULL");
|
||||||
assertTrue(entry->type == ASSET_LOADER_TYPE_JSON, "Invalid type.");
|
assertTrue(entry->type == ASSET_LOADER_TYPE_JSON, "Invalid type.");
|
||||||
|
assertIsMainThread("Must be called from the main thread.");
|
||||||
|
|
||||||
yyjson_doc_free(entry->data.json);
|
yyjson_doc_free(entry->data.json);
|
||||||
entry->data.json = NULL;
|
entry->data.json = NULL;
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
errorret_t assetLocaleLoaderAsync(assetloading_t *loading) {
|
errorret_t assetLocaleLoaderAsync(assetloading_t *loading) {
|
||||||
assertNotNull(loading, "Loading cannot be NULL");
|
assertNotNull(loading, "Loading cannot be NULL");
|
||||||
|
assertNotMainThread("Async loader should not be on main thread.");
|
||||||
|
|
||||||
if(loading->loading.locale.state != ASSET_LOCALE_LOADER_STATE_LOAD_HEADER) {
|
if(loading->loading.locale.state != ASSET_LOCALE_LOADER_STATE_LOAD_HEADER) {
|
||||||
errorOk();
|
errorOk();
|
||||||
@@ -38,6 +39,7 @@ errorret_t assetLocaleLoaderAsync(assetloading_t *loading) {
|
|||||||
errorret_t assetLocaleLoaderSync(assetloading_t *loading) {
|
errorret_t assetLocaleLoaderSync(assetloading_t *loading) {
|
||||||
assertNotNull(loading, "Loading cannot be NULL");
|
assertNotNull(loading, "Loading cannot be NULL");
|
||||||
assertTrue(loading->type == ASSET_LOADER_TYPE_LOCALE, "Invalid type.");
|
assertTrue(loading->type == ASSET_LOADER_TYPE_LOCALE, "Invalid type.");
|
||||||
|
assertIsMainThread("Must be called from the main thread.");
|
||||||
|
|
||||||
switch(loading->loading.locale.state) {
|
switch(loading->loading.locale.state) {
|
||||||
case ASSET_LOCALE_LOADER_STATE_INITIAL:
|
case ASSET_LOCALE_LOADER_STATE_INITIAL:
|
||||||
@@ -60,11 +62,14 @@ errorret_t assetLocaleLoaderSync(assetloading_t *loading) {
|
|||||||
errorret_t assetLocaleDispose(assetentry_t *entry) {
|
errorret_t assetLocaleDispose(assetentry_t *entry) {
|
||||||
assertNotNull(entry, "Asset entry cannot be NULL");
|
assertNotNull(entry, "Asset entry cannot be NULL");
|
||||||
assertTrue(entry->type == ASSET_LOADER_TYPE_LOCALE, "Invalid type.");
|
assertTrue(entry->type == ASSET_LOADER_TYPE_LOCALE, "Invalid type.");
|
||||||
|
assertIsMainThread("Must be called from the main thread.");
|
||||||
|
|
||||||
assetlocalefile_t *localeFile = &entry->data.locale;
|
assetlocalefile_t *localeFile = &entry->data.locale;
|
||||||
errorChain(assetFileClose(&localeFile->file));
|
errorChain(assetFileClose(&localeFile->file));
|
||||||
return assetFileDispose(&localeFile->file);
|
return assetFileDispose(&localeFile->file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// These functions probably need some cleaning;
|
||||||
errorret_t assetLocaleParseHeader(
|
errorret_t assetLocaleParseHeader(
|
||||||
assetlocalefile_t *localeFile,
|
assetlocalefile_t *localeFile,
|
||||||
char_t *headerBuffer,
|
char_t *headerBuffer,
|
||||||
|
|||||||
@@ -9,12 +9,14 @@
|
|||||||
#include "asset/loader/assetloading.h"
|
#include "asset/loader/assetloading.h"
|
||||||
#include "asset/loader/assetentry.h"
|
#include "asset/loader/assetentry.h"
|
||||||
#include "asset/loader/assetloader.h"
|
#include "asset/loader/assetloader.h"
|
||||||
|
#include "script/module/require/modulerequire.h"
|
||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
#include <jerryscript.h>
|
#include <jerryscript.h>
|
||||||
|
|
||||||
errorret_t assetScriptLoaderAsync(assetloading_t *loading) {
|
errorret_t assetScriptLoaderAsync(assetloading_t *loading) {
|
||||||
assertNotNull(loading, "Loading cannot be NULL");
|
assertNotNull(loading, "Loading cannot be NULL");
|
||||||
|
assertNotMainThread("Async loader should not be on main thread.");
|
||||||
|
|
||||||
if(loading->loading.script.state != ASSET_SCRIPT_LOADING_STATE_READ_FILE) {
|
if(loading->loading.script.state != ASSET_SCRIPT_LOADING_STATE_READ_FILE) {
|
||||||
errorOk();
|
errorOk();
|
||||||
@@ -23,33 +25,21 @@ errorret_t assetScriptLoaderAsync(assetloading_t *loading) {
|
|||||||
assertNull(loading->loading.script.buffer, "Buffer already defined?");
|
assertNull(loading->loading.script.buffer, "Buffer already defined?");
|
||||||
|
|
||||||
assetfile_t *file = &loading->loading.script.file;
|
assetfile_t *file = &loading->loading.script.file;
|
||||||
assetLoaderErrorChain(loading, assetFileInit(file, loading->entry->name, NULL, NULL));
|
assetLoaderErrorChain(
|
||||||
assetLoaderErrorChain(loading, assetFileOpen(file));
|
loading, assetFileInit(file, loading->entry->name, NULL, NULL)
|
||||||
|
);
|
||||||
|
|
||||||
size_t capacity = ASSET_SCRIPT_CHUNK_SIZE;
|
uint8_t *buffer = NULL;
|
||||||
uint8_t *buffer = memoryAllocate(capacity + 1);
|
size_t size = 0;
|
||||||
size_t offset = 0;
|
assetLoaderErrorChain(loading, assetFileReadEntire(file, &buffer, &size));
|
||||||
|
|
||||||
while(1) {
|
|
||||||
if(offset + ASSET_SCRIPT_CHUNK_SIZE > capacity) {
|
|
||||||
size_t oldCapacity = capacity + 1;
|
|
||||||
capacity += ASSET_SCRIPT_CHUNK_SIZE;
|
|
||||||
memoryResize((void **)&buffer, oldCapacity, capacity + 1);
|
|
||||||
}
|
|
||||||
assetLoaderErrorChain(loading, assetFileRead(
|
|
||||||
file, buffer + offset, ASSET_SCRIPT_CHUNK_SIZE
|
|
||||||
));
|
|
||||||
size_t chunk = (size_t)file->lastRead;
|
|
||||||
offset += chunk;
|
|
||||||
if(chunk == 0) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer[offset] = '\0';
|
|
||||||
assetLoaderErrorChain(loading, assetFileClose(file));
|
|
||||||
assetLoaderErrorChain(loading, assetFileDispose(file));
|
assetLoaderErrorChain(loading, assetFileDispose(file));
|
||||||
|
|
||||||
|
// Null-terminate for jerry_eval.
|
||||||
|
memoryResize((void **)&buffer, size, size + 1);
|
||||||
|
buffer[size] = '\0';
|
||||||
|
|
||||||
loading->loading.script.buffer = buffer;
|
loading->loading.script.buffer = buffer;
|
||||||
loading->loading.script.size = offset;
|
loading->loading.script.size = size;
|
||||||
loading->loading.script.state = ASSET_SCRIPT_LOADING_STATE_EXEC;
|
loading->loading.script.state = ASSET_SCRIPT_LOADING_STATE_EXEC;
|
||||||
loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC;
|
loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC;
|
||||||
errorOk();
|
errorOk();
|
||||||
@@ -58,6 +48,7 @@ errorret_t assetScriptLoaderAsync(assetloading_t *loading) {
|
|||||||
errorret_t assetScriptLoaderSync(assetloading_t *loading) {
|
errorret_t assetScriptLoaderSync(assetloading_t *loading) {
|
||||||
assertNotNull(loading, "Loading cannot be NULL");
|
assertNotNull(loading, "Loading cannot be NULL");
|
||||||
assertTrue(loading->type == ASSET_LOADER_TYPE_SCRIPT, "Invalid type.");
|
assertTrue(loading->type == ASSET_LOADER_TYPE_SCRIPT, "Invalid type.");
|
||||||
|
assertIsMainThread("Must be called from the main thread.");
|
||||||
|
|
||||||
switch(loading->loading.script.state) {
|
switch(loading->loading.script.state) {
|
||||||
case ASSET_SCRIPT_LOADING_STATE_INITIAL:
|
case ASSET_SCRIPT_LOADING_STATE_INITIAL:
|
||||||
@@ -73,34 +64,58 @@ errorret_t assetScriptLoaderSync(assetloading_t *loading) {
|
|||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get read buffer
|
||||||
uint8_t *buffer = loading->loading.script.buffer;
|
uint8_t *buffer = loading->loading.script.buffer;
|
||||||
assertNotNull(buffer, "Script buffer should have been loaded by now.");
|
assertNotNull(buffer, "Script buffer should have been loaded by now.");
|
||||||
|
|
||||||
|
// Get the current global script realm
|
||||||
|
jerry_value_t global = jerry_current_realm();
|
||||||
|
|
||||||
|
// Replace globalThis.module with a new `module = {}`
|
||||||
|
jerry_value_t oldModule = jerry_object_get_sz(global, "module");
|
||||||
|
|
||||||
|
jerry_value_t module = jerry_object();
|
||||||
|
jerry_object_set_sz(global, "module", module);
|
||||||
|
|
||||||
|
// Eval the script, we handle failure later down the code.
|
||||||
jerry_value_t result = jerry_eval(
|
jerry_value_t result = jerry_eval(
|
||||||
(const jerry_char_t *)buffer,
|
buffer,
|
||||||
loading->loading.script.size,
|
loading->loading.script.size,
|
||||||
JERRY_PARSE_NO_OPTS
|
JERRY_PARSE_NO_OPTS
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Free the read buffer
|
||||||
memoryFree(buffer);
|
memoryFree(buffer);
|
||||||
loading->loading.script.buffer = NULL;
|
loading->loading.script.buffer = NULL;
|
||||||
|
|
||||||
|
// Restore globalThis.module
|
||||||
|
jerry_object_set_sz(global, "module", oldModule);
|
||||||
|
jerry_value_free(oldModule);
|
||||||
|
jerry_value_free(global);
|
||||||
|
|
||||||
if(jerry_value_is_exception(result)) {
|
if(jerry_value_is_exception(result)) {
|
||||||
jerry_value_t errVal = jerry_exception_value(result, false);
|
jerry_value_free(module);
|
||||||
jerry_value_t errStr = jerry_value_to_string(errVal);
|
|
||||||
|
loading->entry->data.script.exports = jerry_undefined();
|
||||||
|
loading->entry->state = ASSET_ENTRY_STATE_ERROR;
|
||||||
|
|
||||||
|
// Get error string
|
||||||
char_t buf[256];
|
char_t buf[256];
|
||||||
jerry_size_t len = jerry_string_to_buffer(
|
moduleBaseExceptionMessage(result, buf, sizeof(buf));
|
||||||
errStr, JERRY_ENCODING_UTF8, (jerry_char_t *)buf, sizeof(buf) - 1
|
|
||||||
);
|
|
||||||
buf[len] = '\0';
|
|
||||||
jerry_value_free(errStr);
|
|
||||||
jerry_value_free(errVal);
|
|
||||||
jerry_value_free(result);
|
jerry_value_free(result);
|
||||||
assetLoaderErrorThrow(loading, "Script error in '%s': %s",
|
assetLoaderErrorThrow(
|
||||||
loading->entry->name, buf
|
loading,
|
||||||
|
"Script execution failed: %s: %s", loading->entry->name, buf
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
loading->entry->data.script = (assetscriptoutput_t)result;
|
// Get module.exports
|
||||||
|
jerry_value_t exports = jerry_object_get_sz(module, "exports");
|
||||||
|
jerry_value_free(result);
|
||||||
|
jerry_value_free(module);
|
||||||
|
|
||||||
|
// Store the exports.
|
||||||
|
loading->entry->data.script.exports = exports;
|
||||||
loading->entry->state = ASSET_ENTRY_STATE_LOADED;
|
loading->entry->state = ASSET_ENTRY_STATE_LOADED;
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
@@ -108,9 +123,15 @@ errorret_t assetScriptLoaderSync(assetloading_t *loading) {
|
|||||||
errorret_t assetScriptDispose(assetentry_t *entry) {
|
errorret_t assetScriptDispose(assetentry_t *entry) {
|
||||||
assertNotNull(entry, "Asset entry cannot be NULL");
|
assertNotNull(entry, "Asset entry cannot be NULL");
|
||||||
assertTrue(entry->type == ASSET_LOADER_TYPE_SCRIPT, "Invalid type.");
|
assertTrue(entry->type == ASSET_LOADER_TYPE_SCRIPT, "Invalid type.");
|
||||||
if(entry->data.script != 0) {
|
assertIsMainThread("Must be called from the main thread.");
|
||||||
jerry_value_free((jerry_value_t)entry->data.script);
|
|
||||||
entry->data.script = 0;
|
if(
|
||||||
|
entry->data.script.exports != 0 &&
|
||||||
|
!jerry_value_is_undefined(entry->data.script.exports)
|
||||||
|
) {
|
||||||
|
jerry_value_free(entry->data.script.exports);
|
||||||
|
entry->data.script.exports = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,14 +7,18 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "asset/assetfile.h"
|
#include "asset/assetfile.h"
|
||||||
|
#include "script/scriptmodule.h"
|
||||||
|
|
||||||
#define ASSET_SCRIPT_CHUNK_SIZE 1024
|
#define ASSET_SCRIPT_CHUNK_SIZE 1024
|
||||||
|
|
||||||
typedef struct assetloading_s assetloading_t;
|
typedef struct assetloading_s assetloading_t;
|
||||||
typedef struct assetentry_s assetentry_t;
|
typedef struct assetentry_s assetentry_t;
|
||||||
|
|
||||||
typedef struct { void *nothing; } assetscriptloaderinput_t;
|
typedef struct {
|
||||||
typedef uint32_t assetscriptoutput_t;
|
void *nothing;
|
||||||
|
} assetscriptloaderinput_t;
|
||||||
|
|
||||||
|
typedef scriptmodule_t assetscriptoutput_t;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
ASSET_SCRIPT_LOADING_STATE_INITIAL,
|
ASSET_SCRIPT_LOADING_STATE_INITIAL,
|
||||||
@@ -30,6 +34,27 @@ typedef struct {
|
|||||||
size_t size;
|
size_t size;
|
||||||
} assetscriptloaderloading_t;
|
} assetscriptloaderloading_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asynchronous loader for a script asset/module.
|
||||||
|
*
|
||||||
|
* @param loading The loading context.
|
||||||
|
* @returns An error code and state.
|
||||||
|
*/
|
||||||
errorret_t assetScriptLoaderAsync(assetloading_t *loading);
|
errorret_t assetScriptLoaderAsync(assetloading_t *loading);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronous loader for a script asset/module. This executes the script after
|
||||||
|
* it has been loaded by the async loader.
|
||||||
|
*
|
||||||
|
* @param loading The loading context.
|
||||||
|
* @returns An error code and state.
|
||||||
|
*/
|
||||||
errorret_t assetScriptLoaderSync(assetloading_t *loading);
|
errorret_t assetScriptLoaderSync(assetloading_t *loading);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disposes of a loaded script asset/module.
|
||||||
|
*
|
||||||
|
* @param entry The asset entry to dispose.
|
||||||
|
* @returns An error code and state.
|
||||||
|
*/
|
||||||
errorret_t assetScriptDispose(assetentry_t *entry);
|
errorret_t assetScriptDispose(assetentry_t *entry);
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
engine_t ENGINE;
|
engine_t ENGINE;
|
||||||
|
|
||||||
errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
||||||
|
assertInit();
|
||||||
memoryZero(&ENGINE, sizeof(engine_t));
|
memoryZero(&ENGINE, sizeof(engine_t));
|
||||||
ENGINE.running = true;
|
ENGINE.running = true;
|
||||||
ENGINE.argc = argc;
|
ENGINE.argc = argc;
|
||||||
@@ -47,19 +48,18 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
|||||||
errorChain(displayInit());
|
errorChain(displayInit());
|
||||||
errorChain(uiInit());
|
errorChain(uiInit());
|
||||||
errorChain(uiTextboxInit());
|
errorChain(uiTextboxInit());
|
||||||
|
|
||||||
errorChain(cutsceneInit());
|
errorChain(cutsceneInit());
|
||||||
errorChain(sceneInit());
|
errorChain(sceneInit());
|
||||||
entityManagerInit();
|
entityManagerInit();
|
||||||
backpackInit();
|
backpackInit();
|
||||||
physicsManagerInit();
|
physicsManagerInit();
|
||||||
errorChain(networkInit());
|
errorChain(networkInit());
|
||||||
|
|
||||||
errorChain(scriptInit());
|
errorChain(scriptInit());
|
||||||
errorChain(scriptExecFile("init.js"));
|
|
||||||
|
|
||||||
consolePrint("Engine initialized");
|
consolePrint("Engine initialized");
|
||||||
|
|
||||||
|
errorChain(scriptExecFile("init.js"));
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,8 +75,9 @@ errorret_t engineUpdate(void) {
|
|||||||
physicsManagerUpdate();
|
physicsManagerUpdate();
|
||||||
errorChain(displayUpdate());
|
errorChain(displayUpdate());
|
||||||
errorChain(cutsceneUpdate());
|
errorChain(cutsceneUpdate());
|
||||||
errorChain(sceneUpdate());
|
sceneUpdate();
|
||||||
errorChain(assetUpdate());
|
errorChain(assetUpdate());
|
||||||
|
errorChain(scriptUpdate());
|
||||||
|
|
||||||
if(inputPressed(INPUT_ACTION_RAGEQUIT)) ENGINE.running = false;
|
if(inputPressed(INPUT_ACTION_RAGEQUIT)) ENGINE.running = false;
|
||||||
errorOk();
|
errorOk();
|
||||||
@@ -97,8 +98,8 @@ errorret_t engineDispose(void) {
|
|||||||
consoleDispose();
|
consoleDispose();
|
||||||
errorChain(displayDispose());
|
errorChain(displayDispose());
|
||||||
// errorChain(saveDispose());
|
// errorChain(saveDispose());
|
||||||
errorChain(assetDispose());
|
|
||||||
errorChain(scriptDispose());
|
errorChain(scriptDispose());
|
||||||
|
errorChain(assetDispose());
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,5 @@
|
|||||||
# https://opensource.org/licenses/MIT
|
# https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
add_subdirectory(display)
|
add_subdirectory(display)
|
||||||
add_subdirectory(overworld)
|
|
||||||
add_subdirectory(physics)
|
add_subdirectory(physics)
|
||||||
add_subdirectory(script)
|
|
||||||
add_subdirectory(trigger)
|
add_subdirectory(trigger)
|
||||||
@@ -36,7 +36,6 @@ void entityRenderableDispose(
|
|||||||
const entityid_t entityId,
|
const entityid_t entityId,
|
||||||
const componentid_t componentId
|
const componentid_t componentId
|
||||||
) {
|
) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void entityRenderableSetType(
|
void entityRenderableSetType(
|
||||||
@@ -80,20 +79,15 @@ void entityRenderableSetDraw(
|
|||||||
r->data.custom.drawUser = user;
|
r->data.custom.drawUser = user;
|
||||||
}
|
}
|
||||||
|
|
||||||
errorret_t entityRenderableDraw(
|
static errorret_t entityRenderableDrawSpritebatch(
|
||||||
const entityid_t entityId,
|
const entityrenderablespritebatch_t *sb
|
||||||
const componentid_t componentId
|
|
||||||
) {
|
) {
|
||||||
entityrenderable_t *r = componentGetData(
|
if(sb->spriteCount == 0) errorOk();
|
||||||
entityId, componentId, COMPONENT_TYPE_RENDERABLE
|
|
||||||
);
|
|
||||||
|
|
||||||
switch(r->type) {
|
|
||||||
case ENTITY_RENDERABLE_TYPE_SPRITEBATCH: {
|
|
||||||
const entityrenderablespritebatch_t *sb = &r->data.spritebatch;
|
|
||||||
errorChain(displaySetState((displaystate_t){
|
errorChain(displaySetState((displaystate_t){
|
||||||
.flags = DISPLAY_STATE_FLAG_BLEND
|
.flags = DISPLAY_STATE_FLAG_BLEND
|
||||||
}));
|
}));
|
||||||
|
|
||||||
spriteBatchClear();
|
spriteBatchClear();
|
||||||
shadermaterial_t mat;
|
shadermaterial_t mat;
|
||||||
memoryZero(&mat, sizeof(shadermaterial_t));
|
memoryZero(&mat, sizeof(shadermaterial_t));
|
||||||
@@ -104,10 +98,11 @@ errorret_t entityRenderableDraw(
|
|||||||
SHADER_LIST_DEFS[SHADER_LIST_SHADER_UNLIT].shader, mat
|
SHADER_LIST_DEFS[SHADER_LIST_SHADER_UNLIT].shader, mat
|
||||||
));
|
));
|
||||||
return spriteBatchFlush();
|
return spriteBatchFlush();
|
||||||
}
|
}
|
||||||
|
|
||||||
case ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL: {
|
static errorret_t entityRenderableDrawMaterial(
|
||||||
const entityrenderablematerial_t *m = &r->data.material;
|
const entityrenderablematerial_t *m
|
||||||
|
) {
|
||||||
errorChain(displaySetState(m->state));
|
errorChain(displaySetState(m->state));
|
||||||
shader_t *shader = SHADER_LIST_DEFS[m->shaderType].shader;
|
shader_t *shader = SHADER_LIST_DEFS[m->shaderType].shader;
|
||||||
assertNotNull(shader, "Shader cannot be null for material type");
|
assertNotNull(shader, "Shader cannot be null for material type");
|
||||||
@@ -117,11 +112,30 @@ errorret_t entityRenderableDraw(
|
|||||||
errorChain(meshDraw(m->meshes[i], m->meshOffsets[i], m->meshCounts[i]));
|
errorChain(meshDraw(m->meshes[i], m->meshOffsets[i], m->meshCounts[i]));
|
||||||
}
|
}
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static errorret_t entityRenderableDrawCustom(
|
||||||
|
const entityid_t entityId,
|
||||||
|
const componentid_t componentId,
|
||||||
|
const entityrenderablecustom_t *custom
|
||||||
|
) {
|
||||||
|
return custom->draw(entityId, componentId, custom->drawUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t entityRenderableDraw(
|
||||||
|
const entityid_t entityId,
|
||||||
|
const componentid_t componentId
|
||||||
|
) {
|
||||||
|
entityrenderable_t *r = componentGetData(
|
||||||
|
entityId, componentId, COMPONENT_TYPE_RENDERABLE
|
||||||
|
);
|
||||||
|
switch(r->type) {
|
||||||
|
case ENTITY_RENDERABLE_TYPE_SPRITEBATCH:
|
||||||
|
return entityRenderableDrawSpritebatch(&r->data.spritebatch);
|
||||||
|
case ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL:
|
||||||
|
return entityRenderableDrawMaterial(&r->data.material);
|
||||||
case ENTITY_RENDERABLE_TYPE_CUSTOM:
|
case ENTITY_RENDERABLE_TYPE_CUSTOM:
|
||||||
return r->data.custom.draw(entityId, componentId, r->data.custom.drawUser);
|
return entityRenderableDrawCustom(entityId, componentId, &r->data.custom);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
assertUnreachable("Invalid renderable type");
|
assertUnreachable("Invalid renderable type");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
# 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
|
|
||||||
entityinteractable.c
|
|
||||||
entityoverworld.c
|
|
||||||
entityoverworldcamera.c
|
|
||||||
entityoverworldtrigger.c
|
|
||||||
entityplayer.c
|
|
||||||
)
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "entityinteractable.h"
|
|
||||||
#include "entity/entitymanager.h"
|
|
||||||
|
|
||||||
void entityInteractableInit(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId
|
|
||||||
) {
|
|
||||||
entityinteractable_t *inter = entityInteractableGet(entityId, componentId);
|
|
||||||
inter->onInteract = NULL;
|
|
||||||
inter->user = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
entityinteractable_t * entityInteractableGet(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId
|
|
||||||
) {
|
|
||||||
return componentGetData(entityId, componentId, COMPONENT_TYPE_INTERACTABLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
void entityInteractableSetCallback(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId,
|
|
||||||
void (*onInteract)(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId,
|
|
||||||
void *user
|
|
||||||
),
|
|
||||||
void *user
|
|
||||||
) {
|
|
||||||
entityinteractable_t *inter = entityInteractableGet(entityId, componentId);
|
|
||||||
inter->onInteract = onInteract;
|
|
||||||
inter->user = user;
|
|
||||||
}
|
|
||||||
|
|
||||||
void entityInteractableTrigger(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId
|
|
||||||
) {
|
|
||||||
entityinteractable_t *inter = entityInteractableGet(entityId, componentId);
|
|
||||||
if(inter->onInteract == NULL) return;
|
|
||||||
inter->onInteract(entityId, componentId, inter->user);
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "entity/entitybase.h"
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
void (*onInteract)(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId,
|
|
||||||
void *user
|
|
||||||
);
|
|
||||||
void *user;
|
|
||||||
} entityinteractable_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the interactable component, clearing the callback and user pointer.
|
|
||||||
*
|
|
||||||
* @param entityId The owning entity.
|
|
||||||
* @param componentId This component's ID.
|
|
||||||
*/
|
|
||||||
void entityInteractableInit(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a pointer to the interactable component data.
|
|
||||||
*
|
|
||||||
* @param entityId The owning entity.
|
|
||||||
* @param componentId This component's ID.
|
|
||||||
* @return Pointer to the entityinteractable_t data.
|
|
||||||
*/
|
|
||||||
entityinteractable_t * entityInteractableGet(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the callback invoked when this interactable is triggered.
|
|
||||||
*
|
|
||||||
* @param entityId The owning entity.
|
|
||||||
* @param componentId This component's ID.
|
|
||||||
* @param onInteract Function called on interaction, or NULL to clear.
|
|
||||||
* @param user Arbitrary pointer forwarded to the callback.
|
|
||||||
*/
|
|
||||||
void entityInteractableSetCallback(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId,
|
|
||||||
void (*onInteract)(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId,
|
|
||||||
void *user
|
|
||||||
),
|
|
||||||
void *user
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fires the interactable's callback if one is set.
|
|
||||||
*
|
|
||||||
* @param entityId The owning entity.
|
|
||||||
* @param componentId This component's ID.
|
|
||||||
*/
|
|
||||||
void entityInteractableTrigger(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId
|
|
||||||
);
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "entityoverworld.h"
|
|
||||||
#include "entity/entitymanager.h"
|
|
||||||
#include "entity/component/display/entityrenderable.h"
|
|
||||||
#include "display/shader/shaderunlit.h"
|
|
||||||
#include "display/mesh/cube.h"
|
|
||||||
|
|
||||||
void entityOverworldInit(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId
|
|
||||||
) {
|
|
||||||
entityoverworld_t *ow = entityOverworldGet(entityId, componentId);
|
|
||||||
ow->type = OVERWORLD_ENTITY_TYPE_NPC;
|
|
||||||
ow->facing = FACING_DIR_DOWN;
|
|
||||||
ow->renderCompId = entityGetComponent(entityId, COMPONENT_TYPE_RENDERABLE);
|
|
||||||
if(ow->renderCompId != COMPONENT_ID_INVALID) {
|
|
||||||
entityRenderableSetDraw(entityId, ow->renderCompId, entityOverworldDraw, NULL);
|
|
||||||
}
|
|
||||||
ow->physCompId = entityGetComponent(entityId, COMPONENT_TYPE_PHYSICS);
|
|
||||||
}
|
|
||||||
|
|
||||||
entityoverworld_t * entityOverworldGet(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId
|
|
||||||
) {
|
|
||||||
return componentGetData(entityId, componentId, COMPONENT_TYPE_OVERWORLD);
|
|
||||||
}
|
|
||||||
|
|
||||||
void entityOverworldSetType(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId,
|
|
||||||
const entityoverworldtype_t type
|
|
||||||
) {
|
|
||||||
entityOverworldGet(entityId, componentId)->type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t entityOverworldDraw(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId,
|
|
||||||
void *user
|
|
||||||
) {
|
|
||||||
componentid_t owCompId = entityGetComponent(entityId, COMPONENT_TYPE_OVERWORLD);
|
|
||||||
entityoverworld_t *ow = entityOverworldGet(entityId, owCompId);
|
|
||||||
|
|
||||||
color_t col = ow->type == OVERWORLD_ENTITY_TYPE_PLAYER ? COLOR_WHITE : COLOR_BLUE;
|
|
||||||
errorChain(shaderSetColor(&SHADER_UNLIT, SHADER_UNLIT_COLOR, col));
|
|
||||||
errorChain(shaderSetTexture(&SHADER_UNLIT, SHADER_UNLIT_TEXTURE, NULL));
|
|
||||||
return meshDraw(&CUBE_MESH_SIMPLE, 0, -1);
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "error/error.h"
|
|
||||||
#include "entity/entitybase.h"
|
|
||||||
#include "overworld/facingdir.h"
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
OVERWORLD_ENTITY_TYPE_PLAYER = 0,
|
|
||||||
OVERWORLD_ENTITY_TYPE_NPC = 1,
|
|
||||||
} entityoverworldtype_t;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
entityoverworldtype_t type;
|
|
||||||
facingdir_t facing;
|
|
||||||
componentid_t renderCompId;
|
|
||||||
componentid_t physCompId;
|
|
||||||
} entityoverworld_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the overworld component, wiring up the draw callback if a
|
|
||||||
* renderable component is already present on the entity.
|
|
||||||
*
|
|
||||||
* @param entityId The owning entity.
|
|
||||||
* @param componentId This component's ID.
|
|
||||||
*/
|
|
||||||
void entityOverworldInit(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a pointer to the overworld component data.
|
|
||||||
*
|
|
||||||
* @param entityId The owning entity.
|
|
||||||
* @param componentId This component's ID.
|
|
||||||
* @return Pointer to the entityoverworld_t data.
|
|
||||||
*/
|
|
||||||
entityoverworld_t * entityOverworldGet(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the overworld entity type.
|
|
||||||
*
|
|
||||||
* @param entityId The owning entity.
|
|
||||||
* @param componentId This component's ID.
|
|
||||||
* @param type The type to assign.
|
|
||||||
*/
|
|
||||||
void entityOverworldSetType(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId,
|
|
||||||
const entityoverworldtype_t type
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draw callback registered on the renderable component.
|
|
||||||
*
|
|
||||||
* @param entityId The owning entity.
|
|
||||||
* @param componentId The renderable component's ID.
|
|
||||||
* @param user Unused.
|
|
||||||
* @return Error result.
|
|
||||||
*/
|
|
||||||
errorret_t entityOverworldDraw(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId,
|
|
||||||
void *user
|
|
||||||
);
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "entityoverworldcamera.h"
|
|
||||||
#include "entity/entitymanager.h"
|
|
||||||
#include "entity/component/display/entityposition.h"
|
|
||||||
#include "entity/component/display/entitycamera.h"
|
|
||||||
|
|
||||||
void entityOverworldCameraInit(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId
|
|
||||||
) {
|
|
||||||
entityoverworldcamera_t *cam = entityOverworldCameraGet(entityId, componentId);
|
|
||||||
cam->targetEntityId = ENTITY_ID_INVALID;
|
|
||||||
cam->targetPosCompId = COMPONENT_ID_INVALID;
|
|
||||||
glm_vec3_zero(cam->targetOffset);
|
|
||||||
glm_vec3_zero(cam->eyeOffset);
|
|
||||||
cam->scale = 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
entityoverworldcamera_t * entityOverworldCameraGet(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId
|
|
||||||
) {
|
|
||||||
return componentGetData(entityId, componentId, COMPONENT_TYPE_OVERWORLD_CAMERA);
|
|
||||||
}
|
|
||||||
|
|
||||||
void entityOverworldCameraSetTarget(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId,
|
|
||||||
const entityid_t targetEntityId,
|
|
||||||
const componentid_t targetPosCompId
|
|
||||||
) {
|
|
||||||
entityoverworldcamera_t *cam = entityOverworldCameraGet(entityId, componentId);
|
|
||||||
cam->targetEntityId = targetEntityId;
|
|
||||||
cam->targetPosCompId = targetPosCompId;
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t entityOverworldCameraRender(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId
|
|
||||||
) {
|
|
||||||
entityoverworldcamera_t *cam = entityOverworldCameraGet(entityId, componentId);
|
|
||||||
|
|
||||||
vec3 targetPos;
|
|
||||||
entityPositionGetWorldPosition(
|
|
||||||
cam->targetEntityId, cam->targetPosCompId, targetPos
|
|
||||||
);
|
|
||||||
|
|
||||||
vec3 center = {
|
|
||||||
targetPos[0] + cam->targetOffset[0],
|
|
||||||
targetPos[1] + cam->targetOffset[1],
|
|
||||||
targetPos[2] + cam->targetOffset[2]
|
|
||||||
};
|
|
||||||
|
|
||||||
componentid_t posComp = entityGetComponent(entityId, COMPONENT_TYPE_POSITION);
|
|
||||||
componentid_t camComp = entityGetComponent(entityId, COMPONENT_TYPE_CAMERA);
|
|
||||||
entityCameraLookAtPixelPerfect(
|
|
||||||
entityId, posComp, camComp, center, cam->eyeOffset, cam->scale
|
|
||||||
);
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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"
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
entityid_t targetEntityId;
|
|
||||||
componentid_t targetPosCompId;
|
|
||||||
vec3 targetOffset;
|
|
||||||
vec3 eyeOffset;
|
|
||||||
float_t scale;
|
|
||||||
} entityoverworldcamera_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the overworld camera component.
|
|
||||||
*
|
|
||||||
* @param entityId The owning entity.
|
|
||||||
* @param componentId This component's ID.
|
|
||||||
*/
|
|
||||||
void entityOverworldCameraInit(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a pointer to the overworld camera component data.
|
|
||||||
*
|
|
||||||
* @param entityId The owning entity.
|
|
||||||
* @param componentId This component's ID.
|
|
||||||
* @return Pointer to the entityoverworldcamera_t data.
|
|
||||||
*/
|
|
||||||
entityoverworldcamera_t * entityOverworldCameraGet(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the entity and position component the camera will follow.
|
|
||||||
*
|
|
||||||
* @param entityId The owning entity.
|
|
||||||
* @param componentId This component's ID.
|
|
||||||
* @param targetEntityId Entity to follow.
|
|
||||||
* @param targetPosCompId Position component on the target entity.
|
|
||||||
*/
|
|
||||||
void entityOverworldCameraSetTarget(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId,
|
|
||||||
const entityid_t targetEntityId,
|
|
||||||
const componentid_t targetPosCompId
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render callback: updates the camera position to track the target entity.
|
|
||||||
* Called automatically each frame via componentRenderAll.
|
|
||||||
*
|
|
||||||
* @param entityId The owning entity.
|
|
||||||
* @param componentId This component's ID.
|
|
||||||
* @return Error state.
|
|
||||||
*/
|
|
||||||
errorret_t entityOverworldCameraRender(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId
|
|
||||||
);
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "entityoverworldtrigger.h"
|
|
||||||
#include "entity/entitymanager.h"
|
|
||||||
#include "entity/component/display/entityposition.h"
|
|
||||||
|
|
||||||
static void entityOverworldTriggerUpdate(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId,
|
|
||||||
void *user
|
|
||||||
) {
|
|
||||||
entityoverworldtrigger_t *t = entityOverworldTriggerGet(entityId, componentId);
|
|
||||||
|
|
||||||
// Find the player position.
|
|
||||||
vec3 playerPos;
|
|
||||||
bool_t playerFound = false;
|
|
||||||
for(entityid_t i = 0; i < ENTITY_COUNT_MAX; i++) {
|
|
||||||
if((ENTITY_MANAGER.entities[i].state & ENTITY_STATE_ACTIVE) == 0) continue;
|
|
||||||
if(entityGetComponent(i, COMPONENT_TYPE_PLAYER) == COMPONENT_ID_INVALID) continue;
|
|
||||||
componentid_t posComp = entityGetComponent(i, COMPONENT_TYPE_POSITION);
|
|
||||||
if(posComp == COMPONENT_ID_INVALID) continue;
|
|
||||||
entityPositionGetWorldPosition(i, posComp, playerPos);
|
|
||||||
playerFound = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool_t wasInside = t->playerInside;
|
|
||||||
bool_t nowInside = playerFound && (
|
|
||||||
playerPos[0] >= t->min[0] && playerPos[0] <= t->max[0] &&
|
|
||||||
playerPos[1] >= t->min[1] && playerPos[1] <= t->max[1] &&
|
|
||||||
playerPos[2] >= t->min[2] && playerPos[2] <= t->max[2]
|
|
||||||
);
|
|
||||||
t->playerInside = nowInside;
|
|
||||||
|
|
||||||
if(nowInside) {
|
|
||||||
if(!wasInside && t->onEnter) t->onEnter(entityId, componentId, t->user);
|
|
||||||
if(t->onStay) t->onStay(entityId, componentId, t->user);
|
|
||||||
} else {
|
|
||||||
if(wasInside && t->onExit) t->onExit(entityId, componentId, t->user);
|
|
||||||
if(t->onOutside) t->onOutside(entityId, componentId, t->user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void entityOverworldTriggerInit(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId
|
|
||||||
) {
|
|
||||||
entityoverworldtrigger_t *t = entityOverworldTriggerGet(entityId, componentId);
|
|
||||||
glm_vec3_zero(t->min);
|
|
||||||
glm_vec3_zero(t->max);
|
|
||||||
t->playerInside = false;
|
|
||||||
t->onEnter = NULL;
|
|
||||||
t->onExit = NULL;
|
|
||||||
t->onStay = NULL;
|
|
||||||
t->onOutside = NULL;
|
|
||||||
t->user = NULL;
|
|
||||||
entityUpdateAdd(entityId, entityOverworldTriggerUpdate, componentId, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
entityoverworldtrigger_t * entityOverworldTriggerGet(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId
|
|
||||||
) {
|
|
||||||
return componentGetData(
|
|
||||||
entityId, componentId, COMPONENT_TYPE_OVERWORLD_TRIGGER
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void entityOverworldTriggerSetBounds(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId,
|
|
||||||
const vec3 min,
|
|
||||||
const vec3 max
|
|
||||||
) {
|
|
||||||
entityoverworldtrigger_t *t = entityOverworldTriggerGet(entityId, componentId);
|
|
||||||
glm_vec3_copy((float_t*)min, t->min);
|
|
||||||
glm_vec3_copy((float_t*)max, t->max);
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "entity/entitybase.h"
|
|
||||||
|
|
||||||
typedef void (*entityoverworldtriggercallback_t)(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId,
|
|
||||||
void *user
|
|
||||||
);
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
vec3 min;
|
|
||||||
vec3 max;
|
|
||||||
bool_t playerInside;
|
|
||||||
entityoverworldtriggercallback_t onEnter;
|
|
||||||
entityoverworldtriggercallback_t onExit;
|
|
||||||
entityoverworldtriggercallback_t onStay;
|
|
||||||
entityoverworldtriggercallback_t onOutside;
|
|
||||||
void *user;
|
|
||||||
} entityoverworldtrigger_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the overworld trigger component and registers its update
|
|
||||||
* callback with the entity manager. The trigger is entirely self-contained
|
|
||||||
* after init — no scene-level wiring required.
|
|
||||||
*/
|
|
||||||
void entityOverworldTriggerInit(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a pointer to the overworld trigger component data.
|
|
||||||
*/
|
|
||||||
entityoverworldtrigger_t * entityOverworldTriggerGet(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the trigger AABB bounds.
|
|
||||||
*/
|
|
||||||
void entityOverworldTriggerSetBounds(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId,
|
|
||||||
const vec3 min,
|
|
||||||
const vec3 max
|
|
||||||
);
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "entityplayer.h"
|
|
||||||
#include "entity/entitymanager.h"
|
|
||||||
#include "entity/component/display/entityposition.h"
|
|
||||||
#include "entity/component/physics/entityphysics.h"
|
|
||||||
#include "entity/component/overworld/entityoverworld.h"
|
|
||||||
#include "entity/component/overworld/entityinteractable.h"
|
|
||||||
#include "input/input.h"
|
|
||||||
|
|
||||||
void entityPlayerInit(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId
|
|
||||||
) {
|
|
||||||
entityplayer_t *player = entityPlayerGet(entityId, componentId);
|
|
||||||
player->speed = ENTITY_PLAYER_SPEED;
|
|
||||||
player->runSpeed = ENTITY_PLAYER_RUN_SPEED;
|
|
||||||
}
|
|
||||||
|
|
||||||
entityplayer_t * entityPlayerGet(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId
|
|
||||||
) {
|
|
||||||
return componentGetData(entityId, componentId, COMPONENT_TYPE_PLAYER);
|
|
||||||
}
|
|
||||||
|
|
||||||
void entityPlayerUpdate(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId
|
|
||||||
) {
|
|
||||||
entityplayer_t *player = entityPlayerGet(entityId, componentId);
|
|
||||||
|
|
||||||
vec2 dir;
|
|
||||||
inputAngle2D(
|
|
||||||
INPUT_ACTION_LEFT, INPUT_ACTION_RIGHT,
|
|
||||||
INPUT_ACTION_UP, INPUT_ACTION_DOWN,
|
|
||||||
dir
|
|
||||||
);
|
|
||||||
|
|
||||||
float_t speed = (
|
|
||||||
inputIsDown(INPUT_ACTION_CANCEL) ? player->runSpeed : player->speed
|
|
||||||
);
|
|
||||||
|
|
||||||
componentid_t owCompId = entityGetComponent(entityId, COMPONENT_TYPE_OVERWORLD);
|
|
||||||
entityoverworld_t *ow = entityOverworldGet(entityId, owCompId);
|
|
||||||
|
|
||||||
if(ow && glm_vec2_norm(dir) > 0.0f) {
|
|
||||||
if(fabsf(dir[0]) >= fabsf(dir[1])) {
|
|
||||||
ow->facing = dir[0] > 0.0f ? FACING_DIR_RIGHT : FACING_DIR_LEFT;
|
|
||||||
} else {
|
|
||||||
ow->facing = dir[1] > 0.0f ? FACING_DIR_DOWN : FACING_DIR_UP;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vec3 vel;
|
|
||||||
entityPhysicsGetVelocity(entityId, ow->physCompId, vel);
|
|
||||||
vel[0] = dir[0] * speed;
|
|
||||||
vel[2] = dir[1] * speed;
|
|
||||||
entityPhysicsSetVelocity(entityId, ow->physCompId, vel);
|
|
||||||
|
|
||||||
if(!inputPressed(INPUT_ACTION_ACCEPT)) return;
|
|
||||||
|
|
||||||
vec3 playerPos;
|
|
||||||
componentid_t playerPosCompId = entityGetComponent(entityId, COMPONENT_TYPE_POSITION);
|
|
||||||
if(playerPosCompId == COMPONENT_ID_INVALID) return;
|
|
||||||
entityPositionGetWorldPosition(entityId, playerPosCompId, playerPos);
|
|
||||||
|
|
||||||
vec2 facingDir;
|
|
||||||
facingDirToVec2(ow ? ow->facing : FACING_DIR_DOWN, facingDir);
|
|
||||||
|
|
||||||
for(entityid_t i = 0; i < ENTITY_COUNT_MAX; i++) {
|
|
||||||
if((ENTITY_MANAGER.entities[i].state & ENTITY_STATE_ACTIVE) == 0) continue;
|
|
||||||
if(i == entityId) continue;
|
|
||||||
|
|
||||||
componentid_t interComp = entityGetComponent(i, COMPONENT_TYPE_INTERACTABLE);
|
|
||||||
if(interComp == COMPONENT_ID_INVALID) continue;
|
|
||||||
|
|
||||||
componentid_t posComp = entityGetComponent(i, COMPONENT_TYPE_POSITION);
|
|
||||||
if(posComp == COMPONENT_ID_INVALID) continue;
|
|
||||||
|
|
||||||
vec3 targetPos;
|
|
||||||
entityPositionGetWorldPosition(i, posComp, targetPos);
|
|
||||||
|
|
||||||
vec2 toTarget = {
|
|
||||||
targetPos[0] - playerPos[0],
|
|
||||||
targetPos[2] - playerPos[2],
|
|
||||||
};
|
|
||||||
float_t forward = glm_vec2_dot(facingDir, toTarget);
|
|
||||||
if(forward <= 0.0f || forward > ENTITY_PLAYER_INTERACT_RANGE) continue;
|
|
||||||
|
|
||||||
float_t lateral = fabsf(
|
|
||||||
facingDir[0] * toTarget[1] - facingDir[1] * toTarget[0]
|
|
||||||
);
|
|
||||||
if(lateral > ENTITY_PLAYER_INTERACT_LATERAL) continue;
|
|
||||||
|
|
||||||
entityInteractableTrigger(i, interComp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "error/error.h"
|
|
||||||
#include "entity/entitybase.h"
|
|
||||||
|
|
||||||
#define ENTITY_PLAYER_SPEED 4.0f
|
|
||||||
#define ENTITY_PLAYER_RUN_SPEED 8.0f
|
|
||||||
#define ENTITY_PLAYER_INTERACT_RANGE 1.5f
|
|
||||||
#define ENTITY_PLAYER_INTERACT_LATERAL 0.6f
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
float_t speed;
|
|
||||||
float_t runSpeed;
|
|
||||||
} entityplayer_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the player component.
|
|
||||||
*
|
|
||||||
* @param entityId The owning entity.
|
|
||||||
* @param componentId This component's ID.
|
|
||||||
*/
|
|
||||||
void entityPlayerInit(const entityid_t entityId, const componentid_t componentId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a pointer to the player component data.
|
|
||||||
*
|
|
||||||
* @param entityId The owning entity.
|
|
||||||
* @param componentId This component's ID.
|
|
||||||
* @return Pointer to the entityplayer_t data.
|
|
||||||
*/
|
|
||||||
entityplayer_t * entityPlayerGet(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads input, moves the player, updates facing direction, and checks for
|
|
||||||
* interactable entities in front of the player when accept is pressed.
|
|
||||||
*
|
|
||||||
* @param entityId The owning entity.
|
|
||||||
* @param componentId This component's ID.
|
|
||||||
*/
|
|
||||||
void entityPlayerUpdate(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId
|
|
||||||
);
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
# Copyright (c) 2026 Dominic Masters
|
|
||||||
#
|
|
||||||
# This software is released under the MIT License.
|
|
||||||
# https://opensource.org/licenses/MIT
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -10,11 +10,6 @@
|
|||||||
#include "entity/component/display/entityrenderable.h"
|
#include "entity/component/display/entityrenderable.h"
|
||||||
#include "entity/component/physics/entityphysics.h"
|
#include "entity/component/physics/entityphysics.h"
|
||||||
#include "entity/component/trigger/entitytrigger.h"
|
#include "entity/component/trigger/entitytrigger.h"
|
||||||
#include "entity/component/overworld/entityoverworld.h"
|
|
||||||
#include "entity/component/overworld/entityoverworldtrigger.h"
|
|
||||||
#include "entity/component/overworld/entityplayer.h"
|
|
||||||
#include "entity/component/overworld/entityinteractable.h"
|
|
||||||
#include "entity/component/overworld/entityoverworldcamera.h"
|
|
||||||
|
|
||||||
// Name (Uppercase)
|
// Name (Uppercase)
|
||||||
// Structure
|
// Structure
|
||||||
@@ -28,8 +23,3 @@ X(CAMERA, entitycamera_t, camera, entityCameraInit, NULL, NULL)
|
|||||||
X(RENDERABLE, entityrenderable_t, renderable, entityRenderableInit, entityRenderableDispose, NULL)
|
X(RENDERABLE, entityrenderable_t, renderable, entityRenderableInit, entityRenderableDispose, NULL)
|
||||||
X(PHYSICS, entityphysics_t, physics, entityPhysicsInit, entityPhysicsDispose, NULL)
|
X(PHYSICS, entityphysics_t, physics, entityPhysicsInit, entityPhysicsDispose, NULL)
|
||||||
X(TRIGGER, entitytrigger_t, trigger, entityTriggerInit, NULL, NULL)
|
X(TRIGGER, entitytrigger_t, trigger, entityTriggerInit, NULL, NULL)
|
||||||
X(OVERWORLD, entityoverworld_t, overworld, entityOverworldInit, NULL, NULL)
|
|
||||||
X(PLAYER, entityplayer_t, player, entityPlayerInit, NULL, NULL)
|
|
||||||
X(INTERACTABLE, entityinteractable_t, interactable, entityInteractableInit, NULL, NULL)
|
|
||||||
X(OVERWORLD_CAMERA, entityoverworldcamera_t, overworldCamera, entityOverworldCameraInit, NULL, entityOverworldCameraRender)
|
|
||||||
X(OVERWORLD_TRIGGER, entityoverworldtrigger_t, overworldTrigger, entityOverworldTriggerInit, NULL, NULL)
|
|
||||||
|
|||||||
+14
-2
@@ -22,8 +22,20 @@ errorret_t inputInit(void) {
|
|||||||
INPUT.actions[i].action = (inputaction_t)i;
|
INPUT.actions[i].action = (inputaction_t)i;
|
||||||
INPUT.actions[i].lastValue = 0.0f;
|
INPUT.actions[i].lastValue = 0.0f;
|
||||||
INPUT.actions[i].currentValue = 0.0f;
|
INPUT.actions[i].currentValue = 0.0f;
|
||||||
eventInit(&INPUT.actions[i].onPressed, INPUT.actions[i].onPressedCallbacks, INPUT.actions[i].onPressedUsers, 4);
|
|
||||||
eventInit(&INPUT.actions[i].onReleased, INPUT.actions[i].onReleasedCallbacks, INPUT.actions[i].onReleasedUsers, 4);
|
eventInit(
|
||||||
|
&INPUT.actions[i].onPressed,
|
||||||
|
INPUT.actions[i].onPressedCallbacks,
|
||||||
|
INPUT.actions[i].onPressedUsers,
|
||||||
|
INPUT_ACTION_CALLBACK_COUNT_MAX
|
||||||
|
);
|
||||||
|
|
||||||
|
eventInit(
|
||||||
|
&INPUT.actions[i].onReleased,
|
||||||
|
INPUT.actions[i].onReleasedCallbacks,
|
||||||
|
INPUT.actions[i].onReleasedUsers,
|
||||||
|
INPUT_ACTION_CALLBACK_COUNT_MAX
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef inputInitPlatform
|
#ifdef inputInitPlatform
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
#include "input/inputactiondefs.h"
|
#include "input/inputactiondefs.h"
|
||||||
#include "event/event.h"
|
#include "event/event.h"
|
||||||
|
|
||||||
|
#define INPUT_ACTION_CALLBACK_COUNT_MAX 4
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
inputaction_t action;
|
inputaction_t action;
|
||||||
float_t lastValue;
|
float_t lastValue;
|
||||||
@@ -20,11 +22,11 @@ typedef struct {
|
|||||||
float_t currentDynamicValue;
|
float_t currentDynamicValue;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
eventcallback_t onPressedCallbacks[4];
|
eventcallback_t onPressedCallbacks[INPUT_ACTION_CALLBACK_COUNT_MAX];
|
||||||
void *onPressedUsers[4];
|
void *onPressedUsers[INPUT_ACTION_CALLBACK_COUNT_MAX];
|
||||||
event_t onPressed;
|
event_t onPressed;
|
||||||
eventcallback_t onReleasedCallbacks[4];
|
eventcallback_t onReleasedCallbacks[INPUT_ACTION_CALLBACK_COUNT_MAX];
|
||||||
void *onReleasedUsers[4];
|
void *onReleasedUsers[INPUT_ACTION_CALLBACK_COUNT_MAX];
|
||||||
event_t onReleased;
|
event_t onReleased;
|
||||||
} inputactiondata_t;
|
} inputactiondata_t;
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,10 @@
|
|||||||
# Copyright (c) 2025 Dominic Masters
|
# Copyright (c) 2026 Dominic Masters
|
||||||
#
|
#
|
||||||
# This software is released under the MIT License.
|
# This software is released under the MIT License.
|
||||||
# https://opensource.org/licenses/MIT
|
# https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
# Sources
|
|
||||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||||
PUBLIC
|
PUBLIC
|
||||||
scene.c
|
scene.c
|
||||||
scenerenderpipeline.c
|
scenerenderpipeline.c
|
||||||
)
|
)
|
||||||
|
|
||||||
# Subdirectories
|
|
||||||
add_subdirectory(initial)
|
|
||||||
add_subdirectory(test)
|
|
||||||
add_subdirectory(overworld)
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "initialscene.h"
|
|
||||||
#include "console/console.h"
|
|
||||||
#include "scene/scene.h"
|
|
||||||
#include "time/time.h"
|
|
||||||
#include "ui/uiloading.h"
|
|
||||||
|
|
||||||
void initialSceneInit(void) {
|
|
||||||
consolePrint("Initial scene initialized");
|
|
||||||
SCENE.data.initial.timer = 0.0f;
|
|
||||||
SCENE.data.initial.hiding = false;
|
|
||||||
uiLoadingShow(NULL, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t initialSceneUpdate(void) {
|
|
||||||
initialscene_t *scene = &SCENE.data.initial;
|
|
||||||
if(scene->hiding) errorOk();
|
|
||||||
|
|
||||||
scene->timer += TIME.delta;
|
|
||||||
if(scene->timer >= INITIAL_SCENE_WAIT) {
|
|
||||||
scene->hiding = true;
|
|
||||||
uiLoadingHide(NULL, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
void initialSceneDispose(void) {
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "error/error.h"
|
|
||||||
|
|
||||||
#define INITIAL_SCENE_WAIT 2.0f
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
float_t timer;
|
|
||||||
bool_t hiding;
|
|
||||||
} initialscene_t;
|
|
||||||
|
|
||||||
void initialSceneInit(void);
|
|
||||||
errorret_t initialSceneUpdate(void);
|
|
||||||
void initialSceneDispose(void);
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
# 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
|
|
||||||
overworldground.c
|
|
||||||
overworldnpc.c
|
|
||||||
overworldplayer.c
|
|
||||||
overworldscene.c
|
|
||||||
)
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "overworldground.h"
|
|
||||||
#include "entity/entitymanager.h"
|
|
||||||
#include "entity/component/physics/entityphysics.h"
|
|
||||||
|
|
||||||
#define OVERWORLD_GROUND_SIZE 20.0f
|
|
||||||
|
|
||||||
void overworldGroundAdd(overworldground_t *ground) {
|
|
||||||
ground->entityId = entityManagerAdd();
|
|
||||||
ground->posCompId = entityAddComponent(
|
|
||||||
ground->entityId, COMPONENT_TYPE_POSITION
|
|
||||||
);
|
|
||||||
entityAddComponent(ground->entityId, COMPONENT_TYPE_RENDERABLE);
|
|
||||||
vec3 pos = { -OVERWORLD_GROUND_SIZE, 0.0f, -OVERWORLD_GROUND_SIZE };
|
|
||||||
vec3 scale = { OVERWORLD_GROUND_SIZE * 2.0f, 1.0f, OVERWORLD_GROUND_SIZE * 2.0f };
|
|
||||||
entityPositionSetLocalPosition(ground->entityId, ground->posCompId, pos);
|
|
||||||
entityPositionSetLocalScale(ground->entityId, ground->posCompId, scale);
|
|
||||||
|
|
||||||
// Separate physics entity centered on the finite ground area.
|
|
||||||
// The visual entity's position is its corner {-size, 0, -size}, not its
|
|
||||||
// center, so a box collider on it would be misplaced. This entity sits at
|
|
||||||
// the true center {0, -0.5, 0} with halfExtents matching the visual extent.
|
|
||||||
ground->floorEntityId = entityManagerAdd();
|
|
||||||
componentid_t floorPosComp = entityAddComponent(
|
|
||||||
ground->floorEntityId, COMPONENT_TYPE_POSITION
|
|
||||||
);
|
|
||||||
componentid_t floorPhysComp = entityAddComponent(
|
|
||||||
ground->floorEntityId, COMPONENT_TYPE_PHYSICS
|
|
||||||
);
|
|
||||||
|
|
||||||
vec3 floorPos = { 0.0f, -0.5f, 0.0f };
|
|
||||||
entityPositionSetLocalPosition(ground->floorEntityId, floorPosComp, floorPos);
|
|
||||||
entityPhysicsSetBodyType(ground->floorEntityId, floorPhysComp, PHYSICS_BODY_STATIC);
|
|
||||||
physicsshape_t shape = {
|
|
||||||
.type = PHYSICS_SHAPE_CUBE,
|
|
||||||
.data.cube = { .halfExtents = {
|
|
||||||
OVERWORLD_GROUND_SIZE,
|
|
||||||
0.5f,
|
|
||||||
OVERWORLD_GROUND_SIZE
|
|
||||||
}}
|
|
||||||
};
|
|
||||||
entityPhysicsSetShape(ground->floorEntityId, floorPhysComp, shape);
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "entity/entitybase.h"
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
entityid_t entityId;
|
|
||||||
componentid_t posCompId;
|
|
||||||
entityid_t floorEntityId;
|
|
||||||
} overworldground_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the ground entity and adds it to the world.
|
|
||||||
*
|
|
||||||
* @param ground The ground state to initialize.
|
|
||||||
*/
|
|
||||||
void overworldGroundAdd(overworldground_t *ground);
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "overworldnpc.h"
|
|
||||||
#include "entity/entitymanager.h"
|
|
||||||
#include "entity/component/physics/entityphysics.h"
|
|
||||||
#include "entity/component/trigger/entitytrigger.h"
|
|
||||||
#include "entity/component/overworld/entityoverworld.h"
|
|
||||||
#include "entity/component/overworld/entityinteractable.h"
|
|
||||||
|
|
||||||
void overworldNpcAdd(overworldnpc_t *npc, vec3 position) {
|
|
||||||
npc->entityId = entityManagerAdd();
|
|
||||||
npc->posCompId = entityAddComponent(npc->entityId, COMPONENT_TYPE_POSITION);
|
|
||||||
entityAddComponent(npc->entityId, COMPONENT_TYPE_RENDERABLE);
|
|
||||||
entityAddComponent(npc->entityId, COMPONENT_TYPE_PHYSICS);
|
|
||||||
npc->overworldCompId = entityAddComponent(npc->entityId, COMPONENT_TYPE_OVERWORLD);
|
|
||||||
npc->triggerCompId = entityAddComponent(npc->entityId, COMPONENT_TYPE_TRIGGER);
|
|
||||||
npc->interactableCompId = entityAddComponent(
|
|
||||||
npc->entityId, COMPONENT_TYPE_INTERACTABLE
|
|
||||||
);
|
|
||||||
|
|
||||||
entityPositionSetLocalPosition(npc->entityId, npc->posCompId, position);
|
|
||||||
|
|
||||||
componentid_t physCompId = entityOverworldGet(npc->entityId, npc->overworldCompId)->physCompId;
|
|
||||||
entityPhysicsSetBodyType(npc->entityId, physCompId, PHYSICS_BODY_STATIC);
|
|
||||||
physicsshape_t shape = {
|
|
||||||
.type = PHYSICS_SHAPE_CAPSULE,
|
|
||||||
.data.capsule = { .radius = 0.4f, .halfHeight = 0.1f }
|
|
||||||
};
|
|
||||||
entityPhysicsSetShape(npc->entityId, physCompId, shape);
|
|
||||||
|
|
||||||
vec3 min = {
|
|
||||||
position[0] - OVERWORLD_NPC_INTERACT_RANGE,
|
|
||||||
position[1] - OVERWORLD_NPC_INTERACT_RANGE,
|
|
||||||
position[2] - OVERWORLD_NPC_INTERACT_RANGE
|
|
||||||
};
|
|
||||||
vec3 max = {
|
|
||||||
position[0] + OVERWORLD_NPC_INTERACT_RANGE,
|
|
||||||
position[1] + OVERWORLD_NPC_INTERACT_RANGE,
|
|
||||||
position[2] + OVERWORLD_NPC_INTERACT_RANGE
|
|
||||||
};
|
|
||||||
entityTriggerSetBounds(npc->entityId, npc->triggerCompId, min, max);
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "entity/entitybase.h"
|
|
||||||
|
|
||||||
#define OVERWORLD_NPC_INTERACT_RANGE 1.5f
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
entityid_t entityId;
|
|
||||||
componentid_t posCompId;
|
|
||||||
componentid_t overworldCompId;
|
|
||||||
componentid_t triggerCompId;
|
|
||||||
componentid_t interactableCompId;
|
|
||||||
} overworldnpc_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the NPC entity at the given world position.
|
|
||||||
*
|
|
||||||
* @param npc The NPC state to initialize.
|
|
||||||
* @param position World-space position to spawn the NPC at.
|
|
||||||
*/
|
|
||||||
void overworldNpcAdd(overworldnpc_t *npc, vec3 position);
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "overworldplayer.h"
|
|
||||||
#include "entity/entitymanager.h"
|
|
||||||
#include "entity/component/physics/entityphysics.h"
|
|
||||||
#include "entity/component/overworld/entityoverworld.h"
|
|
||||||
#include "entity/component/overworld/entityplayer.h"
|
|
||||||
|
|
||||||
void overworldPlayerAdd(overworldplayer_t *player) {
|
|
||||||
player->entityId = entityManagerAdd();
|
|
||||||
player->posCompId = entityAddComponent(player->entityId, COMPONENT_TYPE_POSITION);
|
|
||||||
entityAddComponent(player->entityId, COMPONENT_TYPE_RENDERABLE);
|
|
||||||
entityAddComponent(player->entityId, COMPONENT_TYPE_PHYSICS);
|
|
||||||
componentid_t owCompId = entityAddComponent(player->entityId, COMPONENT_TYPE_OVERWORLD);
|
|
||||||
entityOverworldSetType(player->entityId, owCompId, OVERWORLD_ENTITY_TYPE_PLAYER);
|
|
||||||
player->playerCompId = entityAddComponent(player->entityId, COMPONENT_TYPE_PLAYER);
|
|
||||||
|
|
||||||
vec3 pos = { 0.0f, 0.5f, 0.0f };
|
|
||||||
entityPositionSetLocalPosition(player->entityId, player->posCompId, pos);
|
|
||||||
|
|
||||||
componentid_t physCompId = entityOverworldGet(player->entityId, owCompId)->physCompId;
|
|
||||||
entityPhysicsSetBodyType(player->entityId, physCompId, PHYSICS_BODY_DYNAMIC);
|
|
||||||
physicsshape_t shape = {
|
|
||||||
.type = PHYSICS_SHAPE_CAPSULE,
|
|
||||||
.data.capsule = { .radius = 0.4f, .halfHeight = 0.1f }
|
|
||||||
};
|
|
||||||
entityPhysicsSetShape(player->entityId, physCompId, shape);
|
|
||||||
entityPhysicsGet(player->entityId, physCompId)->gravityScale = 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
void overworldPlayerUpdate(overworldplayer_t *player) {
|
|
||||||
entityPlayerUpdate(player->entityId, player->playerCompId);
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "entity/entitybase.h"
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
entityid_t entityId;
|
|
||||||
componentid_t posCompId;
|
|
||||||
componentid_t playerCompId;
|
|
||||||
} overworldplayer_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the player entity and adds it to the world.
|
|
||||||
*
|
|
||||||
* @param player The player state to initialize.
|
|
||||||
*/
|
|
||||||
void overworldPlayerAdd(overworldplayer_t *player);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the player entity, reading input and moving on the XZ plane.
|
|
||||||
*
|
|
||||||
* @param player The player state to update.
|
|
||||||
*/
|
|
||||||
void overworldPlayerUpdate(overworldplayer_t *player);
|
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "overworldscene.h"
|
|
||||||
#include "overworldplayer.h"
|
|
||||||
#include "overworldground.h"
|
|
||||||
#include "overworldnpc.h"
|
|
||||||
#include "console/console.h"
|
|
||||||
#include "entity/entitymanager.h"
|
|
||||||
#include "entity/component/display/entityrenderable.h"
|
|
||||||
#include "display/shader/shaderlist.h"
|
|
||||||
#include "entity/component/overworld/entityinteractable.h"
|
|
||||||
#include "entity/component/overworld/entityoverworldcamera.h"
|
|
||||||
#include "entity/component/overworld/entityoverworldtrigger.h"
|
|
||||||
#include "display/mesh/cube.h"
|
|
||||||
#include "display/mesh/plane.h"
|
|
||||||
#include "display/displaystate.h"
|
|
||||||
#include "scene/scene.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
|
|
||||||
#define OVERWORLD (SCENE.data.overworld)
|
|
||||||
|
|
||||||
static void overworldSceneTestTriggerEnter(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId,
|
|
||||||
void *user
|
|
||||||
) {
|
|
||||||
consolePrint("Test trigger: player entered");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void overworldSceneTestTriggerExit(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId,
|
|
||||||
void *user
|
|
||||||
) {
|
|
||||||
consolePrint("Test trigger: player exited");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void overworldSceneNpcInteract(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const componentid_t componentId,
|
|
||||||
void *user
|
|
||||||
) {
|
|
||||||
consolePrint("NPC interacted with!");
|
|
||||||
}
|
|
||||||
|
|
||||||
void overworldSceneConfigureShaderMaterial(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const color_t color,
|
|
||||||
mesh_t *mesh
|
|
||||||
) {
|
|
||||||
assertNotNull(mesh, "Mesh cannot be null");
|
|
||||||
componentid_t renderComp = entityGetComponent(
|
|
||||||
entityId, COMPONENT_TYPE_RENDERABLE
|
|
||||||
);
|
|
||||||
entityRenderableSetType(entityId, renderComp, ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL);
|
|
||||||
entityrenderable_t *r = componentGetData(
|
|
||||||
entityId, renderComp, COMPONENT_TYPE_RENDERABLE
|
|
||||||
);
|
|
||||||
r->data.material.shaderType = SHADER_LIST_SHADER_UNLIT;
|
|
||||||
r->data.material.material.unlit.color = color;
|
|
||||||
r->data.material.material.unlit.texture = NULL;
|
|
||||||
r->data.material.state.flags = DISPLAY_STATE_FLAG_DEPTH_TEST;
|
|
||||||
r->data.material.meshes[0] = mesh;
|
|
||||||
r->data.material.meshOffsets[0] = 0;
|
|
||||||
r->data.material.meshCounts[0] = -1;
|
|
||||||
r->data.material.meshCount = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void overworldSceneConfigureSprite(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const color_t color,
|
|
||||||
texture_t *texture
|
|
||||||
) {
|
|
||||||
componentid_t renderComp = entityGetComponent(
|
|
||||||
entityId, COMPONENT_TYPE_RENDERABLE
|
|
||||||
);
|
|
||||||
entityRenderableSetType(entityId, renderComp, ENTITY_RENDERABLE_TYPE_SPRITEBATCH);
|
|
||||||
entityrenderable_t *r = componentGetData(
|
|
||||||
entityId, renderComp, COMPONENT_TYPE_RENDERABLE
|
|
||||||
);
|
|
||||||
r->data.spritebatch.texture = texture;
|
|
||||||
r->data.spritebatch.spriteCount = 1;
|
|
||||||
|
|
||||||
r->data.spritebatch.sprites[0].min[0] = -0.5f;
|
|
||||||
r->data.spritebatch.sprites[0].min[1] = -0.0f;
|
|
||||||
r->data.spritebatch.sprites[0].min[2] = -0.5f;
|
|
||||||
|
|
||||||
r->data.spritebatch.sprites[0].max[0] = 0.5f;
|
|
||||||
r->data.spritebatch.sprites[0].max[1] = 0.0f;
|
|
||||||
r->data.spritebatch.sprites[0].max[2] = 0.5f;
|
|
||||||
|
|
||||||
r->data.spritebatch.sprites[0].uvMin[0] = 0.0f;
|
|
||||||
r->data.spritebatch.sprites[0].uvMin[1] = 0.0f;
|
|
||||||
r->data.spritebatch.sprites[0].uvMax[0] = 1.0f;
|
|
||||||
r->data.spritebatch.sprites[0].uvMax[1] = 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
void overworldSceneInit(void) {
|
|
||||||
consolePrint("Overworld scene initialized");
|
|
||||||
|
|
||||||
overworldGroundAdd(&OVERWORLD.ground);
|
|
||||||
overworldSceneConfigureShaderMaterial(
|
|
||||||
OVERWORLD.ground.entityId, COLOR_MAGENTA, &PLANE_MESH_SIMPLE
|
|
||||||
);
|
|
||||||
|
|
||||||
overworldPlayerAdd(&OVERWORLD.player);
|
|
||||||
overworldSceneConfigureSprite(OVERWORLD.player.entityId, COLOR_GREEN, NULL);
|
|
||||||
|
|
||||||
vec3 npcPos = { 3.0f, 0.5f, 3.0f };
|
|
||||||
overworldNpcAdd(&OVERWORLD.npc, npcPos);
|
|
||||||
overworldSceneConfigureSprite(OVERWORLD.npc.entityId, COLOR_BLUE, NULL);
|
|
||||||
|
|
||||||
OVERWORLD.cameraEntityId = entityManagerAdd();
|
|
||||||
entityAddComponent(OVERWORLD.cameraEntityId, COMPONENT_TYPE_POSITION);
|
|
||||||
entityAddComponent(OVERWORLD.cameraEntityId, COMPONENT_TYPE_CAMERA);
|
|
||||||
OVERWORLD.cameraOverworldCompId = entityAddComponent(
|
|
||||||
OVERWORLD.cameraEntityId, COMPONENT_TYPE_OVERWORLD_CAMERA
|
|
||||||
);
|
|
||||||
entityOverworldCameraSetTarget(
|
|
||||||
OVERWORLD.cameraEntityId, OVERWORLD.cameraOverworldCompId,
|
|
||||||
OVERWORLD.player.entityId, OVERWORLD.player.posCompId
|
|
||||||
);
|
|
||||||
entityoverworldcamera_t *camData = entityOverworldCameraGet(
|
|
||||||
OVERWORLD.cameraEntityId, OVERWORLD.cameraOverworldCompId
|
|
||||||
);
|
|
||||||
glm_vec3_zero(camData->targetOffset);
|
|
||||||
glm_vec3_copy((vec3){ 0.0f, 0.0f, 5.0f }, camData->eyeOffset);
|
|
||||||
camData->scale = 32.0f;
|
|
||||||
entityInteractableSetCallback(
|
|
||||||
OVERWORLD.npc.entityId, OVERWORLD.npc.interactableCompId,
|
|
||||||
overworldSceneNpcInteract, NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
OVERWORLD.testTriggerId = entityManagerAdd();
|
|
||||||
OVERWORLD.testTriggerCompId = entityAddComponent(
|
|
||||||
OVERWORLD.testTriggerId, COMPONENT_TYPE_OVERWORLD_TRIGGER
|
|
||||||
);
|
|
||||||
entityOverworldTriggerSetBounds(
|
|
||||||
OVERWORLD.testTriggerId, OVERWORLD.testTriggerCompId,
|
|
||||||
(vec3){ -2.0f, -1.0f, -2.0f },
|
|
||||||
(vec3){ 2.0f, 1.0f, 2.0f }
|
|
||||||
);
|
|
||||||
entityoverworldtrigger_t *testTrigger = entityOverworldTriggerGet(
|
|
||||||
OVERWORLD.testTriggerId, OVERWORLD.testTriggerCompId
|
|
||||||
);
|
|
||||||
testTrigger->onEnter = overworldSceneTestTriggerEnter;
|
|
||||||
testTrigger->onExit = overworldSceneTestTriggerExit;
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t overworldSceneUpdate(void) {
|
|
||||||
overworldPlayerUpdate(&OVERWORLD.player);
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
void overworldSceneDispose(void) {
|
|
||||||
OVERWORLD.cameraEntityId = ENTITY_ID_INVALID;
|
|
||||||
OVERWORLD.cameraOverworldCompId = COMPONENT_ID_INVALID;
|
|
||||||
OVERWORLD.ground.entityId = ENTITY_ID_INVALID;
|
|
||||||
OVERWORLD.ground.posCompId = COMPONENT_ID_INVALID;
|
|
||||||
OVERWORLD.ground.floorEntityId = ENTITY_ID_INVALID;
|
|
||||||
OVERWORLD.player.entityId = ENTITY_ID_INVALID;
|
|
||||||
OVERWORLD.player.posCompId = COMPONENT_ID_INVALID;
|
|
||||||
OVERWORLD.npc.entityId = ENTITY_ID_INVALID;
|
|
||||||
OVERWORLD.npc.posCompId = COMPONENT_ID_INVALID;
|
|
||||||
OVERWORLD.npc.overworldCompId = COMPONENT_ID_INVALID;
|
|
||||||
OVERWORLD.npc.triggerCompId = COMPONENT_ID_INVALID;
|
|
||||||
OVERWORLD.npc.interactableCompId = COMPONENT_ID_INVALID;
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "error/error.h"
|
|
||||||
#include "entity/entitybase.h"
|
|
||||||
#include "display/color.h"
|
|
||||||
#include "display/mesh/mesh.h"
|
|
||||||
#include "display/texture/texture.h"
|
|
||||||
#include "scene/overworld/overworldplayer.h"
|
|
||||||
#include "scene/overworld/overworldground.h"
|
|
||||||
#include "scene/overworld/overworldnpc.h"
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
entityid_t cameraEntityId;
|
|
||||||
componentid_t cameraOverworldCompId;
|
|
||||||
overworldplayer_t player;
|
|
||||||
overworldground_t ground;
|
|
||||||
overworldnpc_t npc;
|
|
||||||
entityid_t testTriggerId;
|
|
||||||
componentid_t testTriggerCompId;
|
|
||||||
} overworldscene_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configures a SHADER_MATERIAL renderable on an entity with the unlit shader,
|
|
||||||
* depth testing enabled, no blending, and a single mesh.
|
|
||||||
*
|
|
||||||
* @param entityId The entity to configure.
|
|
||||||
* @param color Unlit diffuse color.
|
|
||||||
* @param mesh Mesh to render.
|
|
||||||
*/
|
|
||||||
void overworldSceneConfigureShaderMaterial(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const color_t color,
|
|
||||||
mesh_t *mesh
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configures a SPRITEBATCH renderable on an entity as a single 1x1 billboard
|
|
||||||
* sprite centered at the entity's origin.
|
|
||||||
*
|
|
||||||
* @param entityId The entity to configure.
|
|
||||||
* @param color Per-vertex sprite tint color.
|
|
||||||
* @param texture Texture to use, or NULL for untextured.
|
|
||||||
*/
|
|
||||||
void overworldSceneConfigureSprite(
|
|
||||||
const entityid_t entityId,
|
|
||||||
const color_t color,
|
|
||||||
texture_t *texture
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the overworld scene, spawning all entities and configuring
|
|
||||||
* the camera, player, ground, and NPC.
|
|
||||||
*/
|
|
||||||
void overworldSceneInit(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the overworld scene each frame.
|
|
||||||
*
|
|
||||||
* @return Error state.
|
|
||||||
*/
|
|
||||||
errorret_t overworldSceneUpdate(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disposes the overworld scene, invalidating all entity and component IDs.
|
|
||||||
*/
|
|
||||||
void overworldSceneDispose(void);
|
|
||||||
+5
-59
@@ -6,25 +6,18 @@
|
|||||||
#include "scene.h"
|
#include "scene.h"
|
||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
#include "log/log.h"
|
|
||||||
#include "time/time.h"
|
#include "time/time.h"
|
||||||
#include "display/screen/screen.h"
|
#include "display/screen/screen.h"
|
||||||
#include "entity/entitymanager.h"
|
#include "entity/entitymanager.h"
|
||||||
#include "display/shader/shaderunlit.h"
|
#include "display/shader/shaderunlit.h"
|
||||||
#include "display/display.h"
|
#include "display/display.h"
|
||||||
#include "console/console.h"
|
|
||||||
#include "util/string.h"
|
|
||||||
#include "ui/ui.h"
|
#include "ui/ui.h"
|
||||||
#include "scene/scenerenderpipeline.h"
|
#include "scene/scenerenderpipeline.h"
|
||||||
#include "entity/component.h"
|
#include "entity/component.h"
|
||||||
|
#include "asset/asset.h"
|
||||||
scenefuncs_t SCENE_FUNCTIONS[SCENE_TYPE_COUNT] = {
|
#include "asset/loader/assetloader.h"
|
||||||
{ 0 },
|
#include "script/module/modulebase.h"
|
||||||
#define X(structName, varName, varNameUpper, initFunc, updateFunc, disposeFunc) \
|
#include "console/console.h"
|
||||||
{ initFunc, updateFunc, disposeFunc },
|
|
||||||
#include "scene/scenelist.h"
|
|
||||||
#undef X
|
|
||||||
};
|
|
||||||
|
|
||||||
scene_t SCENE;
|
scene_t SCENE;
|
||||||
|
|
||||||
@@ -33,39 +26,7 @@ errorret_t sceneInit(void) {
|
|||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
errorret_t sceneUpdate(void) {
|
void sceneUpdate(void) {
|
||||||
// Handle scene change
|
|
||||||
if(SCENE.nextType != SCENE_TYPE_NULL) {
|
|
||||||
// Dispose current scene.
|
|
||||||
if(SCENE.type != SCENE_TYPE_NULL) {
|
|
||||||
if(SCENE_FUNCTIONS[SCENE.type].dispose) {
|
|
||||||
SCENE_FUNCTIONS[SCENE.type].dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init new scene
|
|
||||||
SCENE.type = SCENE.nextType;
|
|
||||||
SCENE.nextType = SCENE_TYPE_NULL;
|
|
||||||
|
|
||||||
if(SCENE.type != SCENE_TYPE_NULL) {
|
|
||||||
if(SCENE_FUNCTIONS[SCENE.type].init) {
|
|
||||||
SCENE_FUNCTIONS[SCENE.type].init();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update scene
|
|
||||||
#ifdef DUSK_TIME_DYNAMIC
|
|
||||||
if(TIME.dynamicUpdate) {
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if(SCENE.type != SCENE_TYPE_NULL && SCENE_FUNCTIONS[SCENE.type].update) {
|
|
||||||
errorChain(SCENE_FUNCTIONS[SCENE.type].update());
|
|
||||||
}
|
|
||||||
|
|
||||||
errorOk();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
errorret_t sceneRender(void) {
|
errorret_t sceneRender(void) {
|
||||||
@@ -103,21 +64,6 @@ errorret_t sceneRender(void) {
|
|||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
void sceneSet(const scenetype_t type) {
|
|
||||||
assertTrue(
|
|
||||||
type > SCENE_TYPE_NULL && type < SCENE_TYPE_COUNT,
|
|
||||||
"Invalid scene type"
|
|
||||||
);
|
|
||||||
SCENE.nextType = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t sceneDispose(void) {
|
errorret_t sceneDispose(void) {
|
||||||
if(SCENE.type != SCENE_TYPE_NULL) {
|
|
||||||
if(SCENE_FUNCTIONS[SCENE.type].dispose) {
|
|
||||||
SCENE_FUNCTIONS[SCENE.type].dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SCENE.type = SCENE_TYPE_NULL;
|
|
||||||
SCENE.nextType = SCENE_TYPE_NULL;
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-52
@@ -6,75 +6,31 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "asset/assetfile.h"
|
#include "error/error.h"
|
||||||
#include "scene/initial/initialscene.h"
|
|
||||||
#include "scene/test/testscene.h"
|
|
||||||
#include "scene/overworld/overworldscene.h"
|
|
||||||
|
|
||||||
#define SCENE_EVENT_UPDATE_MAX 16
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
SCENE_TYPE_NULL,
|
|
||||||
#define X(structName, varName, varNameUpper, initFunc, updateFunc, disposeFunc) \
|
|
||||||
SCENE_TYPE_##varNameUpper,
|
|
||||||
#include "scene/scenelist.h"
|
|
||||||
#undef X
|
|
||||||
SCENE_TYPE_COUNT
|
|
||||||
} scenetype_t;
|
|
||||||
|
|
||||||
typedef union {
|
|
||||||
#define X(structName, varName, varNameUpper, initFunc, updateFunc, disposeFunc) \
|
|
||||||
structName varName;
|
|
||||||
#include "scene/scenelist.h"
|
|
||||||
#undef X
|
|
||||||
} scenedata_t;
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
void (*init)(void);
|
void *nothing;
|
||||||
errorret_t (*update)(void);
|
|
||||||
void (*dispose)(void);
|
|
||||||
} scenefuncs_t;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
scenedata_t data;
|
|
||||||
scenetype_t type;
|
|
||||||
scenetype_t nextType;
|
|
||||||
} scene_t;
|
} scene_t;
|
||||||
|
|
||||||
extern scenefuncs_t SCENE_FUNCTIONS[SCENE_TYPE_COUNT];
|
|
||||||
extern scene_t SCENE;
|
extern scene_t SCENE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the scene manager.
|
* Initialises the scene manager.
|
||||||
*
|
|
||||||
* @return Any error state that happened.
|
|
||||||
*/
|
*/
|
||||||
errorret_t sceneInit(void);
|
errorret_t sceneInit(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ticks the scene manager; may call the scene's update method.
|
* Ticks the scene manager. Processes any pending scene transition, then
|
||||||
*
|
* calls scriptSceneUpdate on the active scene.
|
||||||
* @return Any error state that happened.
|
|
||||||
*/
|
*/
|
||||||
errorret_t sceneUpdate(void);
|
void sceneUpdate(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the scene.
|
* Renders the current scene (entities, render pipeline, UI).
|
||||||
*
|
|
||||||
* @return Any error state that happened.
|
|
||||||
*/
|
*/
|
||||||
errorret_t sceneRender(void);
|
errorret_t sceneRender(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests a scene change on the next safe opportunity.
|
* Disposes the active scene immediately.
|
||||||
*
|
|
||||||
* @param type The type of scene to change to.
|
|
||||||
*/
|
|
||||||
void sceneSet(const scenetype_t type);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disposes of the current scene.
|
|
||||||
*
|
|
||||||
* @return Any error state that happened.
|
|
||||||
*/
|
*/
|
||||||
errorret_t sceneDispose(void);
|
errorret_t sceneDispose(void);
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef X
|
|
||||||
#define X(structName, varName, varNameUpper, initFunc, updateFunc, disposeFunc) \
|
|
||||||
((void))
|
|
||||||
#endif
|
|
||||||
|
|
||||||
X(initialscene_t, initial, INITIAL, initialSceneInit, initialSceneUpdate, initialSceneDispose)
|
|
||||||
X(testscene_t, test, TEST, testSceneInit, testSceneUpdate, testSceneDispose)
|
|
||||||
X(overworldscene_t, overworld, OVERWORLD, overworldSceneInit, overworldSceneUpdate, overworldSceneDispose)
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "testscene.h"
|
|
||||||
#include "console/console.h"
|
|
||||||
#include "entity/entitymanager.h"
|
|
||||||
#include "input/input.h"
|
|
||||||
|
|
||||||
#define TEST_SCENE_ENTITY_MAX (ENTITY_COUNT_MAX - 1)
|
|
||||||
|
|
||||||
static entityid_t cameraEntityId;
|
|
||||||
static componentid_t cameraCompId;
|
|
||||||
static entityid_t testEntities[TEST_SCENE_ENTITY_MAX];
|
|
||||||
static uint8_t testEntityCount = 0;
|
|
||||||
|
|
||||||
static void testSceneSpawnTestEntity(void) {
|
|
||||||
if(testEntityCount >= TEST_SCENE_ENTITY_MAX) return;
|
|
||||||
|
|
||||||
entityid_t entity = entityManagerAdd();
|
|
||||||
componentid_t posComp = entityAddComponent(entity, COMPONENT_TYPE_POSITION);
|
|
||||||
entityAddComponent(entity, COMPONENT_TYPE_RENDERABLE);
|
|
||||||
|
|
||||||
const int32_t cols = 20;
|
|
||||||
const float_t spacing = 1.5f;
|
|
||||||
int32_t col = testEntityCount % cols;
|
|
||||||
int32_t row = testEntityCount / cols;
|
|
||||||
vec3 pos = {
|
|
||||||
((float_t)col - (cols - 1) * 0.5f) * spacing,
|
|
||||||
0.0f,
|
|
||||||
(float_t)row * spacing
|
|
||||||
};
|
|
||||||
entityPositionSetLocalPosition(entity, posComp, pos);
|
|
||||||
|
|
||||||
testEntities[testEntityCount++] = entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void testSceneUpdateCamera(void) {
|
|
||||||
if(testEntityCount == 0) return;
|
|
||||||
|
|
||||||
float_t minX = FLT_MAX, maxX = -FLT_MAX;
|
|
||||||
float_t minZ = FLT_MAX, maxZ = -FLT_MAX;
|
|
||||||
|
|
||||||
for(entityid_t i = 0; i < testEntityCount; i++) {
|
|
||||||
componentid_t posComp = entityGetComponent(
|
|
||||||
testEntities[i], COMPONENT_TYPE_POSITION
|
|
||||||
);
|
|
||||||
if(posComp == COMPONENT_ID_INVALID) continue;
|
|
||||||
vec3 pos;
|
|
||||||
entityPositionGetLocalPosition(testEntities[i], posComp, pos);
|
|
||||||
if(pos[0] < minX) minX = pos[0];
|
|
||||||
if(pos[0] > maxX) maxX = pos[0];
|
|
||||||
if(pos[2] < minZ) minZ = pos[2];
|
|
||||||
if(pos[2] > maxZ) maxZ = pos[2];
|
|
||||||
}
|
|
||||||
|
|
||||||
float_t centerX = (minX + maxX) * 0.5f;
|
|
||||||
float_t centerZ = (minZ + maxZ) * 0.5f;
|
|
||||||
float_t extentX = (maxX - minX) * 0.5f + 0.5f;
|
|
||||||
float_t extentZ = (maxZ - minZ) * 0.5f + 0.5f;
|
|
||||||
float_t extent = extentX > extentZ ? extentX : extentZ;
|
|
||||||
float_t dist = extent * 1.5f + 2.0f;
|
|
||||||
|
|
||||||
vec3 target = { centerX, 0.0f, centerZ };
|
|
||||||
vec3 eye = { centerX + dist, dist, centerZ + dist };
|
|
||||||
vec3 up = { 0.0f, 1.0f, 0.0f };
|
|
||||||
entityPositionLookAt(cameraEntityId, cameraCompId, eye, target, up);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testSceneInit(void) {
|
|
||||||
consolePrint("Test scene initialized");
|
|
||||||
testEntityCount = 0;
|
|
||||||
|
|
||||||
cameraEntityId = entityManagerAdd();
|
|
||||||
cameraCompId = entityAddComponent(cameraEntityId, COMPONENT_TYPE_POSITION);
|
|
||||||
entityAddComponent(cameraEntityId, COMPONENT_TYPE_CAMERA);
|
|
||||||
|
|
||||||
vec3 eye, target, up;
|
|
||||||
glm_vec3_zero(target);
|
|
||||||
glm_vec3_copy((vec3){ 3.0f, 3.0f, 3.0f }, eye);
|
|
||||||
glm_vec3_copy((vec3){ 0.0f, 1.0f, 0.0f }, up);
|
|
||||||
entityPositionLookAt(cameraEntityId, cameraCompId, eye, target, up);
|
|
||||||
|
|
||||||
for(int i = 0; i < 5; i++) testSceneSpawnTestEntity();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t testSceneUpdate(void) {
|
|
||||||
if(inputPressed(INPUT_ACTION_ACCEPT)) {
|
|
||||||
for(int i = 0; i < 5; i++) testSceneSpawnTestEntity();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(inputPressed(INPUT_ACTION_CANCEL)) {
|
|
||||||
for(int i = 0; i < 5 && testEntityCount > 0; i++) {
|
|
||||||
entityDispose(testEntities[--testEntityCount]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
testSceneUpdateCamera();
|
|
||||||
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
void testSceneDispose(void) {
|
|
||||||
testEntityCount = 0;
|
|
||||||
cameraEntityId = ENTITY_ID_INVALID;
|
|
||||||
cameraCompId = COMPONENT_ID_INVALID;
|
|
||||||
}
|
|
||||||
@@ -6,4 +6,9 @@
|
|||||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||||
PUBLIC
|
PUBLIC
|
||||||
modulebase.c
|
modulebase.c
|
||||||
|
modulelist.c
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Subdirs
|
||||||
|
add_subdirectory(event)
|
||||||
|
add_subdirectory(require)
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "script/module/modulebase.h"
|
||||||
|
#include "script/scriptproto.h"
|
||||||
|
#include "script/module/display/moduletexture.h"
|
||||||
|
#include "script/module/asset/moduleassetentry.h"
|
||||||
|
#include "script/module/asset/moduleassetbatch.h"
|
||||||
|
#include "asset/asset.h"
|
||||||
|
#include "asset/loader/assetloader.h"
|
||||||
|
|
||||||
|
static scriptproto_t MODULE_ASSET_PROTO;
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleAssetExists) {
|
||||||
|
moduleBaseRequireArgs(1);
|
||||||
|
moduleBaseRequireString(0);
|
||||||
|
|
||||||
|
char_t buf[256];
|
||||||
|
moduleBaseToString(args[0], buf, sizeof(buf));
|
||||||
|
|
||||||
|
return jerry_boolean(assetFileExists(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleAssetLock) {
|
||||||
|
moduleBaseRequireArgs(2);
|
||||||
|
moduleBaseRequireString(0);
|
||||||
|
moduleBaseRequireNumber(1);
|
||||||
|
|
||||||
|
char_t buf[256];
|
||||||
|
moduleBaseToString(args[0], buf, sizeof(buf));
|
||||||
|
assetloadertype_t type = (assetloadertype_t)moduleBaseArgInt(1);
|
||||||
|
|
||||||
|
assetloaderinput_t input;
|
||||||
|
assetloaderinput_t *inputPtr = NULL;
|
||||||
|
|
||||||
|
if(argc >= 3 && jerry_value_is_number(args[2])) {
|
||||||
|
int32_t inputVal = moduleBaseArgInt(2);
|
||||||
|
switch(type) {
|
||||||
|
case ASSET_LOADER_TYPE_TEXTURE:
|
||||||
|
input.texture = (textureformat_t)inputVal;
|
||||||
|
inputPtr = &input;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ASSET_LOADER_TYPE_MESH:
|
||||||
|
input.mesh = (assetmeshinputaxis_t)inputVal;
|
||||||
|
inputPtr = &input;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assetentry_t *entry = assetLock(buf, type, inputPtr);
|
||||||
|
if(!entry) return moduleBaseThrow("Asset.lock: failed to lock asset");
|
||||||
|
jsassetentry_t e = { .entry = entry };
|
||||||
|
|
||||||
|
return scriptProtoCreateValue(&MODULE_ASSET_ENTRY_PROTO, &e);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleAssetRequireLoaded) {
|
||||||
|
moduleBaseRequireArgs(1);
|
||||||
|
|
||||||
|
jsassetentry_t *e = (jsassetentry_t *)scriptProtoGetValue(
|
||||||
|
&MODULE_ASSET_ENTRY_PROTO, args[0]
|
||||||
|
);
|
||||||
|
if(!e || !e->entry) {
|
||||||
|
return moduleBaseThrow("Asset.requireLoaded: expected AssetEntry");
|
||||||
|
}
|
||||||
|
errorret_t err = assetRequireLoaded(e->entry);
|
||||||
|
if(errorIsNotOk(err)) return moduleBaseThrowError(err);
|
||||||
|
jerry_value_t ref = jerry_value_copy(args[0]);
|
||||||
|
|
||||||
|
return ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleAssetUnlock) {
|
||||||
|
moduleBaseRequireArgs(1);
|
||||||
|
moduleBaseRequireString(0);
|
||||||
|
char_t buf[256];
|
||||||
|
moduleBaseToString(args[0], buf, sizeof(buf));
|
||||||
|
assetUnlock(buf);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void moduleAssetInit(void) {
|
||||||
|
moduleAssetEntryInit();
|
||||||
|
moduleAssetBatchInit();
|
||||||
|
scriptProtoInit(&MODULE_ASSET_PROTO, "Asset", sizeof(uint8_t), NULL);
|
||||||
|
scriptProtoDefineStaticFunc(&MODULE_ASSET_PROTO, "exists", moduleAssetExists);
|
||||||
|
scriptProtoDefineStaticFunc(&MODULE_ASSET_PROTO, "lock", moduleAssetLock);
|
||||||
|
scriptProtoDefineStaticFunc(&MODULE_ASSET_PROTO, "unlock", moduleAssetUnlock);
|
||||||
|
scriptProtoDefineStaticFunc(&MODULE_ASSET_PROTO, "requireLoaded", moduleAssetRequireLoaded);
|
||||||
|
|
||||||
|
jerry_value_t global = MODULE_ASSET_PROTO.prototype;
|
||||||
|
|
||||||
|
/* Asset.TYPE_* loader type constants */
|
||||||
|
struct { const char_t *name; int val; } types[] = {
|
||||||
|
{ "TYPE_MESH", ASSET_LOADER_TYPE_MESH },
|
||||||
|
{ "TYPE_TEXTURE", ASSET_LOADER_TYPE_TEXTURE },
|
||||||
|
{ "TYPE_TILESET", ASSET_LOADER_TYPE_TILESET },
|
||||||
|
{ "TYPE_LOCALE", ASSET_LOADER_TYPE_LOCALE },
|
||||||
|
{ "TYPE_JSON", ASSET_LOADER_TYPE_JSON },
|
||||||
|
{ "TYPE_SCRIPT", ASSET_LOADER_TYPE_SCRIPT },
|
||||||
|
};
|
||||||
|
for(int i = 0; i < 6; i++) {
|
||||||
|
jerry_value_t k = jerry_string_sz(types[i].name);
|
||||||
|
jerry_value_t v = jerry_number((double)types[i].val);
|
||||||
|
jerry_object_set(global, k, v);
|
||||||
|
jerry_value_free(v);
|
||||||
|
jerry_value_free(k);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Asset.MESH_AXIS_* input constants for TYPE_MESH */
|
||||||
|
struct { const char_t *name; int val; } axes[] = {
|
||||||
|
{ "MESH_AXIS_Y_UP", MESH_INPUT_AXIS_Y_UP },
|
||||||
|
{ "MESH_AXIS_Z_UP", MESH_INPUT_AXIS_Z_UP },
|
||||||
|
{ "MESH_AXIS_X_UP", MESH_INPUT_AXIS_X_UP },
|
||||||
|
{ "MESH_AXIS_Y_DOWN", MESH_INPUT_AXIS_Y_DOWN },
|
||||||
|
{ "MESH_AXIS_Z_DOWN", MESH_INPUT_AXIS_Z_DOWN },
|
||||||
|
{ "MESH_AXIS_X_DOWN", MESH_INPUT_AXIS_X_DOWN },
|
||||||
|
};
|
||||||
|
for(int i = 0; i < 6; i++) {
|
||||||
|
jerry_value_t k = jerry_string_sz(axes[i].name);
|
||||||
|
jerry_value_t v = jerry_number((double)axes[i].val);
|
||||||
|
jerry_object_set(global, k, v);
|
||||||
|
jerry_value_free(v);
|
||||||
|
jerry_value_free(k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void moduleAssetDispose(void) {
|
||||||
|
scriptProtoDispose(&MODULE_ASSET_PROTO);
|
||||||
|
moduleAssetBatchDispose();
|
||||||
|
moduleAssetEntryDispose();
|
||||||
|
}
|
||||||
@@ -0,0 +1,293 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "script/module/modulebase.h"
|
||||||
|
#include "script/scriptproto.h"
|
||||||
|
#include "script/module/asset/moduleassetentry.h"
|
||||||
|
#include "asset/assetbatch.h"
|
||||||
|
#include "asset/asset.h"
|
||||||
|
#include "asset/loader/assetloader.h"
|
||||||
|
#include "util/memory.h"
|
||||||
|
|
||||||
|
static scriptproto_t MODULE_ASSET_BATCH_PROTO;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
assetbatch_t *batch;
|
||||||
|
} jsassetbatch_t;
|
||||||
|
|
||||||
|
static void moduleAssetBatchFree(void *ptr, jerry_object_native_info_t *info) {
|
||||||
|
(void)info;
|
||||||
|
jsassetbatch_t *b = (jsassetbatch_t *)ptr;
|
||||||
|
if(b && b->batch) {
|
||||||
|
assetBatchDispose(b->batch);
|
||||||
|
memoryFree(b->batch);
|
||||||
|
}
|
||||||
|
memoryFree(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline jsassetbatch_t *moduleAssetBatchSelf(
|
||||||
|
const jerry_call_info_t *callInfo
|
||||||
|
) {
|
||||||
|
return (jsassetbatch_t *)scriptProtoGetValue(
|
||||||
|
&MODULE_ASSET_BATCH_PROTO, callInfo->this_value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AssetBatch(descriptors[])
|
||||||
|
*
|
||||||
|
* Parses an array of {path, type, format?/input?/axis?} descriptor objects,
|
||||||
|
* locks all assets, and returns an AssetBatch JS object. Works with and
|
||||||
|
* without `new`.
|
||||||
|
*/
|
||||||
|
moduleBaseFunction(moduleAssetBatchCtor) {
|
||||||
|
if(argc < 1 || !jerry_value_is_array(args[0])) {
|
||||||
|
return moduleBaseThrow("AssetBatch: expected an array of descriptors");
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t count = jerry_array_length(args[0]);
|
||||||
|
if(count == 0 || count > (uint32_t)ASSET_BATCH_COUNT_MAX) {
|
||||||
|
return moduleBaseThrow("AssetBatch: descriptor count out of range");
|
||||||
|
}
|
||||||
|
|
||||||
|
assetbatchdesc_t descs[ASSET_BATCH_COUNT_MAX];
|
||||||
|
char_t paths[ASSET_BATCH_COUNT_MAX][ASSET_FILE_NAME_MAX];
|
||||||
|
|
||||||
|
for(uint32_t i = 0; i < count; i++) {
|
||||||
|
jerry_value_t desc = jerry_object_get_index(args[0], i);
|
||||||
|
if(!jerry_value_is_object(desc)) {
|
||||||
|
jerry_value_free(desc);
|
||||||
|
return moduleBaseThrow("AssetBatch: each descriptor must be an object");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* path */
|
||||||
|
jerry_value_t pathProp = moduleBaseGetProp(desc, "path");
|
||||||
|
if(!jerry_value_is_string(pathProp)) {
|
||||||
|
jerry_value_free(pathProp);
|
||||||
|
jerry_value_free(desc);
|
||||||
|
return moduleBaseThrow("AssetBatch: descriptor.path must be a string");
|
||||||
|
}
|
||||||
|
jerry_size_t pathLen = jerry_string_to_buffer(
|
||||||
|
pathProp, JERRY_ENCODING_UTF8,
|
||||||
|
(jerry_char_t *)paths[i], ASSET_FILE_NAME_MAX - 1
|
||||||
|
);
|
||||||
|
paths[i][pathLen] = '\0';
|
||||||
|
jerry_value_free(pathProp);
|
||||||
|
descs[i].path = paths[i];
|
||||||
|
|
||||||
|
/* type */
|
||||||
|
jerry_value_t typeProp = moduleBaseGetProp(desc, "type");
|
||||||
|
if(!jerry_value_is_number(typeProp)) {
|
||||||
|
jerry_value_free(typeProp);
|
||||||
|
jerry_value_free(desc);
|
||||||
|
return moduleBaseThrow("AssetBatch: descriptor.type must be a number");
|
||||||
|
}
|
||||||
|
descs[i].type = (assetloadertype_t)moduleBaseValueInt(typeProp);
|
||||||
|
jerry_value_free(typeProp);
|
||||||
|
|
||||||
|
/* input: accept format, input, or axis — whichever is present */
|
||||||
|
memoryZero(&descs[i].input, sizeof(assetloaderinput_t));
|
||||||
|
jerry_value_t inputProp = moduleBaseGetProp(desc, "format");
|
||||||
|
if(jerry_value_is_undefined(inputProp)) {
|
||||||
|
jerry_value_free(inputProp);
|
||||||
|
inputProp = moduleBaseGetProp(desc, "input");
|
||||||
|
}
|
||||||
|
if(jerry_value_is_undefined(inputProp)) {
|
||||||
|
jerry_value_free(inputProp);
|
||||||
|
inputProp = moduleBaseGetProp(desc, "axis");
|
||||||
|
}
|
||||||
|
if(jerry_value_is_number(inputProp)) {
|
||||||
|
int32_t v = moduleBaseValueInt(inputProp);
|
||||||
|
switch(descs[i].type) {
|
||||||
|
case ASSET_LOADER_TYPE_TEXTURE:
|
||||||
|
descs[i].input.texture = (textureformat_t)v;
|
||||||
|
break;
|
||||||
|
case ASSET_LOADER_TYPE_MESH:
|
||||||
|
descs[i].input.mesh = (assetmeshinputaxis_t)v;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jerry_value_free(inputProp);
|
||||||
|
jerry_value_free(desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
assetbatch_t *batch = (assetbatch_t *)memoryAllocate(sizeof(assetbatch_t));
|
||||||
|
assetBatchInit(batch, (uint16_t)count, descs);
|
||||||
|
|
||||||
|
jsassetbatch_t init = { .batch = batch };
|
||||||
|
return scriptProtoCreateValue(&MODULE_ASSET_BATCH_PROTO, &init);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Properties ---------------------------------------------------------- */
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleAssetBatchGetCount) {
|
||||||
|
(void)args; (void)argc;
|
||||||
|
jsassetbatch_t *b = moduleAssetBatchSelf(callInfo);
|
||||||
|
if(!b || !b->batch) return jerry_number(0.0);
|
||||||
|
return jerry_number((double)b->batch->count);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleAssetBatchGetIsLoaded) {
|
||||||
|
(void)args; (void)argc;
|
||||||
|
jsassetbatch_t *b = moduleAssetBatchSelf(callInfo);
|
||||||
|
if(!b || !b->batch) return jerry_boolean(false);
|
||||||
|
return jerry_boolean(assetBatchIsLoaded(b->batch));
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleAssetBatchGetHasError) {
|
||||||
|
(void)args; (void)argc;
|
||||||
|
jsassetbatch_t *b = moduleAssetBatchSelf(callInfo);
|
||||||
|
if(!b || !b->batch) return jerry_boolean(false);
|
||||||
|
return jerry_boolean(assetBatchHasError(b->batch));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Methods ------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/* Blocks until every entry is loaded. Returns this for chaining. */
|
||||||
|
moduleBaseFunction(moduleAssetBatchRequireLoaded) {
|
||||||
|
(void)args; (void)argc;
|
||||||
|
jsassetbatch_t *b = moduleAssetBatchSelf(callInfo);
|
||||||
|
if(!b || !b->batch) {
|
||||||
|
return moduleBaseThrow("AssetBatch.requireLoaded: batch already disposed");
|
||||||
|
}
|
||||||
|
errorret_t err = assetBatchRequireLoaded(b->batch);
|
||||||
|
if(errorIsNotOk(err)) return moduleBaseThrowError(err);
|
||||||
|
return jerry_value_copy(callInfo->this_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Acquires an additional lock on every entry. Returns this for chaining. */
|
||||||
|
moduleBaseFunction(moduleAssetBatchLock) {
|
||||||
|
(void)args; (void)argc;
|
||||||
|
jsassetbatch_t *b = moduleAssetBatchSelf(callInfo);
|
||||||
|
if(!b || !b->batch) return jerry_undefined();
|
||||||
|
assetBatchLock(b->batch);
|
||||||
|
return jerry_value_copy(callInfo->this_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Releases all locks and clears the batch. The object becomes invalid. */
|
||||||
|
moduleBaseFunction(moduleAssetBatchUnlock) {
|
||||||
|
(void)args; (void)argc;
|
||||||
|
jsassetbatch_t *b = moduleAssetBatchSelf(callInfo);
|
||||||
|
if(!b || !b->batch) return jerry_undefined();
|
||||||
|
assetBatchDispose(b->batch);
|
||||||
|
memoryFree(b->batch);
|
||||||
|
b->batch = NULL;
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns the AssetEntry at index i, adding an independent lock to it. */
|
||||||
|
moduleBaseFunction(moduleAssetBatchEntry) {
|
||||||
|
moduleBaseRequireArgs(1);
|
||||||
|
jsassetbatch_t *b = moduleAssetBatchSelf(callInfo);
|
||||||
|
if(!b || !b->batch) return jerry_undefined();
|
||||||
|
uint32_t idx = (uint32_t)moduleBaseArgInt(0);
|
||||||
|
if(idx >= (uint32_t)b->batch->count) return jerry_undefined();
|
||||||
|
assetentry_t *entry = b->batch->entries[idx];
|
||||||
|
if(!entry) return jerry_undefined();
|
||||||
|
assetEntryLock(entry);
|
||||||
|
jsassetentry_t e = { .entry = entry };
|
||||||
|
return scriptProtoCreateValue(&MODULE_ASSET_ENTRY_PROTO, &e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Event proxies ------------------------------------------------------- */
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleAssetBatchGetOnLoaded) {
|
||||||
|
jsassetbatch_t *b = moduleAssetBatchSelf(callInfo);
|
||||||
|
if(!b || !b->batch) return jerry_undefined();
|
||||||
|
return moduleEventProxyGetOrCreate(callInfo, &b->batch->onLoaded, "_onLoaded");
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleAssetBatchGetOnEntryLoaded) {
|
||||||
|
jsassetbatch_t *b = moduleAssetBatchSelf(callInfo);
|
||||||
|
if(!b || !b->batch) return jerry_undefined();
|
||||||
|
return moduleEventProxyGetOrCreate(callInfo, &b->batch->onEntryLoaded, "_onEntryLoaded");
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleAssetBatchGetOnError) {
|
||||||
|
jsassetbatch_t *b = moduleAssetBatchSelf(callInfo);
|
||||||
|
if(!b || !b->batch) return jerry_undefined();
|
||||||
|
return moduleEventProxyGetOrCreate(callInfo, &b->batch->onError, "_onError");
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleAssetBatchGetOnEntryError) {
|
||||||
|
jsassetbatch_t *b = moduleAssetBatchSelf(callInfo);
|
||||||
|
if(!b || !b->batch) return jerry_undefined();
|
||||||
|
return moduleEventProxyGetOrCreate(callInfo, &b->batch->onEntryError, "_onEntryError");
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleAssetBatchToString) {
|
||||||
|
jsassetbatch_t *b = moduleAssetBatchSelf(callInfo);
|
||||||
|
if(!b || !b->batch) return jerry_string_sz("AssetBatch:invalid");
|
||||||
|
char_t buf[32];
|
||||||
|
snprintf(buf, sizeof(buf), "AssetBatch(%u)", (unsigned)b->batch->count);
|
||||||
|
return jerry_string_sz(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Init / Dispose ------------------------------------------------------ */
|
||||||
|
|
||||||
|
static void moduleAssetBatchInit(void) {
|
||||||
|
scriptProtoInit(
|
||||||
|
&MODULE_ASSET_BATCH_PROTO, "AssetBatch",
|
||||||
|
sizeof(jsassetbatch_t), moduleAssetBatchCtor
|
||||||
|
);
|
||||||
|
MODULE_ASSET_BATCH_PROTO.info.free_cb = moduleAssetBatchFree;
|
||||||
|
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_ASSET_BATCH_PROTO, "count",
|
||||||
|
moduleAssetBatchGetCount, NULL
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_ASSET_BATCH_PROTO, "isLoaded",
|
||||||
|
moduleAssetBatchGetIsLoaded, NULL
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_ASSET_BATCH_PROTO, "hasError",
|
||||||
|
moduleAssetBatchGetHasError, NULL
|
||||||
|
);
|
||||||
|
scriptProtoDefineFunc(
|
||||||
|
&MODULE_ASSET_BATCH_PROTO, "requireLoaded",
|
||||||
|
moduleAssetBatchRequireLoaded
|
||||||
|
);
|
||||||
|
scriptProtoDefineFunc(
|
||||||
|
&MODULE_ASSET_BATCH_PROTO, "lock",
|
||||||
|
moduleAssetBatchLock
|
||||||
|
);
|
||||||
|
scriptProtoDefineFunc(
|
||||||
|
&MODULE_ASSET_BATCH_PROTO, "unlock",
|
||||||
|
moduleAssetBatchUnlock
|
||||||
|
);
|
||||||
|
scriptProtoDefineFunc(
|
||||||
|
&MODULE_ASSET_BATCH_PROTO, "entry",
|
||||||
|
moduleAssetBatchEntry
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_ASSET_BATCH_PROTO, "onLoaded",
|
||||||
|
moduleAssetBatchGetOnLoaded, NULL
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_ASSET_BATCH_PROTO, "onEntryLoaded",
|
||||||
|
moduleAssetBatchGetOnEntryLoaded, NULL
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_ASSET_BATCH_PROTO, "onError",
|
||||||
|
moduleAssetBatchGetOnError, NULL
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_ASSET_BATCH_PROTO, "onEntryError",
|
||||||
|
moduleAssetBatchGetOnEntryError, NULL
|
||||||
|
);
|
||||||
|
scriptProtoDefineToString(
|
||||||
|
&MODULE_ASSET_BATCH_PROTO, moduleAssetBatchToString
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void moduleAssetBatchDispose(void) {
|
||||||
|
scriptProtoDispose(&MODULE_ASSET_BATCH_PROTO);
|
||||||
|
}
|
||||||
@@ -0,0 +1,204 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "script/module/modulebase.h"
|
||||||
|
#include "script/scriptproto.h"
|
||||||
|
#include "script/module/display/moduletexture.h"
|
||||||
|
#include "script/module/asset/moduleeventproxy.h"
|
||||||
|
#include "asset/asset.h"
|
||||||
|
#include "asset/loader/assetloader.h"
|
||||||
|
#include "asset/loader/assetentry.h"
|
||||||
|
#include "util/memory.h"
|
||||||
|
|
||||||
|
static scriptproto_t MODULE_ASSET_ENTRY_PROTO;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
assetentry_t *entry;
|
||||||
|
} jsassetentry_t;
|
||||||
|
|
||||||
|
/** Releases the asset lock when the JS object is GC'd. */
|
||||||
|
static void moduleAssetEntryFree(
|
||||||
|
void *ptr,
|
||||||
|
jerry_object_native_info_t *info
|
||||||
|
) {
|
||||||
|
(void)info;
|
||||||
|
jsassetentry_t *e = (jsassetentry_t *)ptr;
|
||||||
|
if(e && e->entry) {
|
||||||
|
assetUnlockEntry(e->entry);
|
||||||
|
e->entry = NULL;
|
||||||
|
}
|
||||||
|
memoryFree(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleAssetEntryCtor) {
|
||||||
|
(void)callInfo; (void)args; (void)argc;
|
||||||
|
return moduleBaseThrow("AssetEntry cannot be instantiated with new");
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline jsassetentry_t *moduleAssetEntrySelf(
|
||||||
|
const jerry_call_info_t *callInfo
|
||||||
|
) {
|
||||||
|
return (jsassetentry_t *)scriptProtoGetValue(
|
||||||
|
&MODULE_ASSET_ENTRY_PROTO, callInfo->this_value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleAssetEntryGetName) {
|
||||||
|
jsassetentry_t *e = moduleAssetEntrySelf(callInfo);
|
||||||
|
if(!e || !e->entry) return jerry_undefined();
|
||||||
|
return jerry_string_sz(e->entry->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleAssetEntryGetState) {
|
||||||
|
jsassetentry_t *e = moduleAssetEntrySelf(callInfo);
|
||||||
|
if(!e || !e->entry) return jerry_undefined();
|
||||||
|
return jerry_number((double)e->entry->state);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleAssetEntryGetType) {
|
||||||
|
jsassetentry_t *e = moduleAssetEntrySelf(callInfo);
|
||||||
|
if(!e || !e->entry) return jerry_undefined();
|
||||||
|
return jerry_number((double)e->entry->type);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleAssetEntryGetIsLoaded) {
|
||||||
|
jsassetentry_t *e = moduleAssetEntrySelf(callInfo);
|
||||||
|
if(!e || !e->entry) return jerry_boolean(false);
|
||||||
|
return jerry_boolean(e->entry->state == ASSET_ENTRY_STATE_LOADED);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* requireLoaded() — blocks until fully loaded, returns this for chaining. */
|
||||||
|
moduleBaseFunction(moduleAssetEntryRequireLoaded) {
|
||||||
|
jsassetentry_t *e = moduleAssetEntrySelf(callInfo);
|
||||||
|
if(!e || !e->entry) return moduleBaseThrow("AssetEntry.requireLoaded: invalid entry");
|
||||||
|
errorret_t err = assetRequireLoaded(e->entry);
|
||||||
|
if(errorIsNotOk(err)) return moduleBaseThrowError(err);
|
||||||
|
jerry_value_t self = jerry_value_copy(callInfo->this_value);
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* texture — returns a Texture wrapping this entry's loaded texture data.
|
||||||
|
* Returns undefined if the entry is not a texture or not yet loaded.
|
||||||
|
* Locks the entry a second time so the Texture holds its own independent
|
||||||
|
* reference; the lock is released when the Texture is GC'd.
|
||||||
|
*/
|
||||||
|
moduleBaseFunction(moduleAssetEntryGetTexture) {
|
||||||
|
jsassetentry_t *e = moduleAssetEntrySelf(callInfo);
|
||||||
|
if(!e || !e->entry) return jerry_undefined();
|
||||||
|
if(e->entry->type != ASSET_LOADER_TYPE_TEXTURE) return jerry_undefined();
|
||||||
|
if(e->entry->state != ASSET_ENTRY_STATE_LOADED) return jerry_undefined();
|
||||||
|
assetEntryLock(e->entry);
|
||||||
|
jstexture_t tex = { .entry = e->entry };
|
||||||
|
return scriptProtoCreateValue(&MODULE_TEXTURE_PROTO, &tex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* unlock() — releases the lock early; subsequent access is undefined. */
|
||||||
|
moduleBaseFunction(moduleAssetEntryUnlock) {
|
||||||
|
jsassetentry_t *e = moduleAssetEntrySelf(callInfo);
|
||||||
|
if(!e || !e->entry) return jerry_undefined();
|
||||||
|
assetUnlockEntry(e->entry);
|
||||||
|
e->entry = NULL;
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleAssetEntryGetOnLoaded) {
|
||||||
|
jsassetentry_t *e = moduleAssetEntrySelf(callInfo);
|
||||||
|
if(!e || !e->entry) return jerry_undefined();
|
||||||
|
return moduleEventProxyGetOrCreate(callInfo, &e->entry->onLoaded, "_onLoaded");
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleAssetEntryGetOnUnloaded) {
|
||||||
|
jsassetentry_t *e = moduleAssetEntrySelf(callInfo);
|
||||||
|
if(!e || !e->entry) return jerry_undefined();
|
||||||
|
return moduleEventProxyGetOrCreate(callInfo, &e->entry->onUnloaded, "_onUnloaded");
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleAssetEntryGetOnError) {
|
||||||
|
jsassetentry_t *e = moduleAssetEntrySelf(callInfo);
|
||||||
|
if(!e || !e->entry) return jerry_undefined();
|
||||||
|
return moduleEventProxyGetOrCreate(callInfo, &e->entry->onError, "_onError");
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleAssetEntryToString) {
|
||||||
|
jsassetentry_t *e = moduleAssetEntrySelf(callInfo);
|
||||||
|
if(!e || !e->entry) return jerry_string_sz("AssetEntry:invalid");
|
||||||
|
char_t buf[64];
|
||||||
|
snprintf(buf, sizeof(buf), "AssetEntry(%s)", e->entry->name);
|
||||||
|
return jerry_string_sz(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void moduleAssetEntryInit(void) {
|
||||||
|
moduleEventProxyInit();
|
||||||
|
scriptProtoInit(
|
||||||
|
&MODULE_ASSET_ENTRY_PROTO, "AssetEntry",
|
||||||
|
sizeof(jsassetentry_t), moduleAssetEntryCtor
|
||||||
|
);
|
||||||
|
MODULE_ASSET_ENTRY_PROTO.info.free_cb = moduleAssetEntryFree;
|
||||||
|
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_ASSET_ENTRY_PROTO, "name", moduleAssetEntryGetName, NULL
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_ASSET_ENTRY_PROTO, "state", moduleAssetEntryGetState, NULL
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_ASSET_ENTRY_PROTO, "type", moduleAssetEntryGetType, NULL
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_ASSET_ENTRY_PROTO, "isLoaded", moduleAssetEntryGetIsLoaded, NULL
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_ASSET_ENTRY_PROTO, "texture", moduleAssetEntryGetTexture, NULL
|
||||||
|
);
|
||||||
|
scriptProtoDefineFunc(
|
||||||
|
&MODULE_ASSET_ENTRY_PROTO, "requireLoaded", moduleAssetEntryRequireLoaded
|
||||||
|
);
|
||||||
|
scriptProtoDefineFunc(
|
||||||
|
&MODULE_ASSET_ENTRY_PROTO, "unlock", moduleAssetEntryUnlock
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_ASSET_ENTRY_PROTO, "onLoaded",
|
||||||
|
moduleAssetEntryGetOnLoaded, NULL
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_ASSET_ENTRY_PROTO, "onUnloaded",
|
||||||
|
moduleAssetEntryGetOnUnloaded, NULL
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_ASSET_ENTRY_PROTO, "onError",
|
||||||
|
moduleAssetEntryGetOnError, NULL
|
||||||
|
);
|
||||||
|
scriptProtoDefineToString(
|
||||||
|
&MODULE_ASSET_ENTRY_PROTO, moduleAssetEntryToString
|
||||||
|
);
|
||||||
|
|
||||||
|
/* State constants */
|
||||||
|
jerry_value_t ctor = MODULE_ASSET_ENTRY_PROTO.constructor;
|
||||||
|
struct { const char_t *name; int val; } states[] = {
|
||||||
|
{ "NOT_STARTED", ASSET_ENTRY_STATE_NOT_STARTED },
|
||||||
|
{ "PENDING", ASSET_ENTRY_STATE_PENDING_ASYNC },
|
||||||
|
{ "LOADING", ASSET_ENTRY_STATE_LOADING_ASYNC },
|
||||||
|
{ "LOADED", ASSET_ENTRY_STATE_LOADED },
|
||||||
|
{ "ERROR", ASSET_ENTRY_STATE_ERROR },
|
||||||
|
};
|
||||||
|
for(int i = 0; i < 5; i++) {
|
||||||
|
jerry_value_t k = jerry_string_sz(states[i].name);
|
||||||
|
jerry_value_t v = jerry_number((double)states[i].val);
|
||||||
|
jerry_object_set(ctor, k, v);
|
||||||
|
jerry_value_free(v);
|
||||||
|
jerry_value_free(k);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void moduleAssetEntryDispose(void) {
|
||||||
|
scriptProtoDispose(&MODULE_ASSET_ENTRY_PROTO);
|
||||||
|
moduleEventProxyDispose();
|
||||||
|
}
|
||||||
@@ -0,0 +1,245 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "script/module/modulebase.h"
|
||||||
|
#include "script/scriptproto.h"
|
||||||
|
#include "event/event.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum number of JS subscriber slots per proxy. Must be >= the largest
|
||||||
|
* event capacity used (ASSET_ENTRY_EVENT_MAX, ASSET_BATCH_EVENT_MAX, etc.).
|
||||||
|
*/
|
||||||
|
#define MODULE_EVENT_PROXY_MAX_SLOTS 4
|
||||||
|
|
||||||
|
static scriptproto_t MODULE_EVENT_PROXY_PROTO;
|
||||||
|
|
||||||
|
/** Native data stored on each EventProxy JS object. */
|
||||||
|
typedef struct {
|
||||||
|
event_t *event;
|
||||||
|
jerry_value_t fns[MODULE_EVENT_PROXY_MAX_SLOTS];
|
||||||
|
} jseventproxy_t;
|
||||||
|
|
||||||
|
/* ---- C trampolines (one per slot index) ---------------------------------- */
|
||||||
|
|
||||||
|
static void moduleEventProxyTrampoline0(void *params, void *user) {
|
||||||
|
(void)params;
|
||||||
|
jerry_value_t fn = (jerry_value_t)(uintptr_t)user;
|
||||||
|
jerry_value_t ret = jerry_call(fn, jerry_undefined(), NULL, 0);
|
||||||
|
jerry_value_free(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void moduleEventProxyTrampoline1(void *params, void *user) {
|
||||||
|
(void)params;
|
||||||
|
jerry_value_t fn = (jerry_value_t)(uintptr_t)user;
|
||||||
|
jerry_value_t ret = jerry_call(fn, jerry_undefined(), NULL, 0);
|
||||||
|
jerry_value_free(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void moduleEventProxyTrampoline2(void *params, void *user) {
|
||||||
|
(void)params;
|
||||||
|
jerry_value_t fn = (jerry_value_t)(uintptr_t)user;
|
||||||
|
jerry_value_t ret = jerry_call(fn, jerry_undefined(), NULL, 0);
|
||||||
|
jerry_value_free(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void moduleEventProxyTrampoline3(void *params, void *user) {
|
||||||
|
(void)params;
|
||||||
|
jerry_value_t fn = (jerry_value_t)(uintptr_t)user;
|
||||||
|
jerry_value_t ret = jerry_call(fn, jerry_undefined(), NULL, 0);
|
||||||
|
jerry_value_free(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
static eventcallback_t MODULE_EVENT_PROXY_TRAMPOLINES[MODULE_EVENT_PROXY_MAX_SLOTS] = {
|
||||||
|
moduleEventProxyTrampoline0,
|
||||||
|
moduleEventProxyTrampoline1,
|
||||||
|
moduleEventProxyTrampoline2,
|
||||||
|
moduleEventProxyTrampoline3,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ---- GC free callback ---------------------------------------------------- */
|
||||||
|
|
||||||
|
static void moduleEventProxyFree(void *ptr, jerry_object_native_info_t *info) {
|
||||||
|
(void)info;
|
||||||
|
jseventproxy_t *ep = (jseventproxy_t *)ptr;
|
||||||
|
if(ep) {
|
||||||
|
for(uint32_t i = 0; i < MODULE_EVENT_PROXY_MAX_SLOTS; i++) {
|
||||||
|
if(jerry_value_is_function(ep->fns[i])) {
|
||||||
|
if(ep->event) eventUnsubscribe(ep->event, MODULE_EVENT_PROXY_TRAMPOLINES[i]);
|
||||||
|
jerry_value_free(ep->fns[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
memoryFree(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Self helper --------------------------------------------------------- */
|
||||||
|
|
||||||
|
static inline jseventproxy_t *moduleEventProxySelf(
|
||||||
|
const jerry_call_info_t *callInfo
|
||||||
|
) {
|
||||||
|
return (jseventproxy_t *)scriptProtoGetValue(
|
||||||
|
&MODULE_EVENT_PROXY_PROTO, callInfo->this_value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Slot get/set helpers ------------------------------------------------ */
|
||||||
|
|
||||||
|
static inline jerry_value_t moduleEventProxyGetSlot(
|
||||||
|
const jerry_call_info_t *callInfo,
|
||||||
|
const uint32_t slot
|
||||||
|
) {
|
||||||
|
jseventproxy_t *ep = moduleEventProxySelf(callInfo);
|
||||||
|
if(!ep || !ep->event || slot >= ep->event->size) return jerry_null();
|
||||||
|
return jerry_value_is_function(ep->fns[slot])
|
||||||
|
? jerry_value_copy(ep->fns[slot])
|
||||||
|
: jerry_null();
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline jerry_value_t moduleEventProxySetSlot(
|
||||||
|
const jerry_call_info_t *callInfo,
|
||||||
|
const jerry_value_t args[],
|
||||||
|
const jerry_length_t argc,
|
||||||
|
const uint32_t slot
|
||||||
|
) {
|
||||||
|
jseventproxy_t *ep = moduleEventProxySelf(callInfo);
|
||||||
|
if(!ep || !ep->event || slot >= ep->event->size) return jerry_undefined();
|
||||||
|
|
||||||
|
if(jerry_value_is_function(ep->fns[slot])) {
|
||||||
|
eventUnsubscribe(ep->event, MODULE_EVENT_PROXY_TRAMPOLINES[slot]);
|
||||||
|
jerry_value_free(ep->fns[slot]);
|
||||||
|
ep->fns[slot] = jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
jerry_value_t val = (argc > 0) ? args[0] : jerry_undefined();
|
||||||
|
if(jerry_value_is_function(val)) {
|
||||||
|
ep->fns[slot] = jerry_value_copy(val);
|
||||||
|
eventSubscribe(
|
||||||
|
ep->event,
|
||||||
|
MODULE_EVENT_PROXY_TRAMPOLINES[slot],
|
||||||
|
(void *)(uintptr_t)ep->fns[slot]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Per-slot getter/setter pairs ---------------------------------------- */
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleEventProxyGet0) {
|
||||||
|
(void)args; (void)argc;
|
||||||
|
return moduleEventProxyGetSlot(callInfo, 0);
|
||||||
|
}
|
||||||
|
moduleBaseFunction(moduleEventProxySet0) {
|
||||||
|
return moduleEventProxySetSlot(callInfo, args, argc, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleEventProxyGet1) {
|
||||||
|
(void)args; (void)argc;
|
||||||
|
return moduleEventProxyGetSlot(callInfo, 1);
|
||||||
|
}
|
||||||
|
moduleBaseFunction(moduleEventProxySet1) {
|
||||||
|
return moduleEventProxySetSlot(callInfo, args, argc, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleEventProxyGet2) {
|
||||||
|
(void)args; (void)argc;
|
||||||
|
return moduleEventProxyGetSlot(callInfo, 2);
|
||||||
|
}
|
||||||
|
moduleBaseFunction(moduleEventProxySet2) {
|
||||||
|
return moduleEventProxySetSlot(callInfo, args, argc, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleEventProxyGet3) {
|
||||||
|
(void)args; (void)argc;
|
||||||
|
return moduleEventProxyGetSlot(callInfo, 3);
|
||||||
|
}
|
||||||
|
moduleBaseFunction(moduleEventProxySet3) {
|
||||||
|
return moduleEventProxySetSlot(callInfo, args, argc, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleEventProxyGetLength) {
|
||||||
|
(void)args; (void)argc;
|
||||||
|
jseventproxy_t *ep = moduleEventProxySelf(callInfo);
|
||||||
|
if(!ep || !ep->event) return jerry_number(0.0);
|
||||||
|
return jerry_number((double)ep->event->size);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleEventProxyToString) {
|
||||||
|
(void)args; (void)argc;
|
||||||
|
return jerry_string_sz("EventProxy");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Lazy-create helper (shared by all parent types) --------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the event proxy pinned at pinKey on callInfo->this_value.
|
||||||
|
* Creates and pins it on first access. event must remain valid for the
|
||||||
|
* lifetime of the parent JS object (it is stored by pointer, not copied).
|
||||||
|
*/
|
||||||
|
static inline jerry_value_t moduleEventProxyGetOrCreate(
|
||||||
|
const jerry_call_info_t *callInfo,
|
||||||
|
event_t *event,
|
||||||
|
const char_t *pinKey
|
||||||
|
) {
|
||||||
|
jerry_value_t keyStr = jerry_string_sz(pinKey);
|
||||||
|
jerry_value_t existing = jerry_object_get(callInfo->this_value, keyStr);
|
||||||
|
if(!jerry_value_is_undefined(existing)) {
|
||||||
|
jerry_value_free(keyStr);
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
jerry_value_free(existing);
|
||||||
|
|
||||||
|
jseventproxy_t ep;
|
||||||
|
ep.event = event;
|
||||||
|
for(uint32_t i = 0; i < MODULE_EVENT_PROXY_MAX_SLOTS; i++) {
|
||||||
|
ep.fns[i] = jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
jerry_value_t proxy = scriptProtoCreateValue(&MODULE_EVENT_PROXY_PROTO, &ep);
|
||||||
|
jerry_object_set(callInfo->this_value, keyStr, proxy);
|
||||||
|
jerry_value_free(keyStr);
|
||||||
|
return proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Init / Dispose ------------------------------------------------------ */
|
||||||
|
|
||||||
|
static void moduleEventProxyInit(void) {
|
||||||
|
scriptProtoInit(
|
||||||
|
&MODULE_EVENT_PROXY_PROTO, NULL,
|
||||||
|
sizeof(jseventproxy_t), NULL
|
||||||
|
);
|
||||||
|
MODULE_EVENT_PROXY_PROTO.info.free_cb = moduleEventProxyFree;
|
||||||
|
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_EVENT_PROXY_PROTO, "0",
|
||||||
|
moduleEventProxyGet0, moduleEventProxySet0
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_EVENT_PROXY_PROTO, "1",
|
||||||
|
moduleEventProxyGet1, moduleEventProxySet1
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_EVENT_PROXY_PROTO, "2",
|
||||||
|
moduleEventProxyGet2, moduleEventProxySet2
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_EVENT_PROXY_PROTO, "3",
|
||||||
|
moduleEventProxyGet3, moduleEventProxySet3
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_EVENT_PROXY_PROTO, "length",
|
||||||
|
moduleEventProxyGetLength, NULL
|
||||||
|
);
|
||||||
|
scriptProtoDefineToString(
|
||||||
|
&MODULE_EVENT_PROXY_PROTO, moduleEventProxyToString
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void moduleEventProxyDispose(void) {
|
||||||
|
scriptProtoDispose(&MODULE_EVENT_PROXY_PROTO);
|
||||||
|
}
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "script/module/modulebase.h"
|
||||||
|
#include "script/scriptproto.h"
|
||||||
|
#include "util/memory.h"
|
||||||
|
#include "display/color.h"
|
||||||
|
|
||||||
|
static scriptproto_t MODULE_COLOR_PROTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the native color_t pointer from a Color JS instance.
|
||||||
|
* Returns NULL if the value is not a Color.
|
||||||
|
*/
|
||||||
|
static inline color_t *moduleColorFrom(const jerry_value_t val) {
|
||||||
|
return (color_t *)scriptProtoGetValue(&MODULE_COLOR_PROTO, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Color JS object from a C color_t value.
|
||||||
|
*/
|
||||||
|
static inline jerry_value_t moduleColorPush(const color_t c) {
|
||||||
|
return scriptProtoCreateValue(&MODULE_COLOR_PROTO, &c);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleColorConstructor) {
|
||||||
|
color_t *ptr = (color_t *)memoryAllocate(sizeof(color_t));
|
||||||
|
ptr->r = (uint8_t)moduleBaseOptInt(0, 0);
|
||||||
|
ptr->g = (uint8_t)moduleBaseOptInt(1, 0);
|
||||||
|
ptr->b = (uint8_t)moduleBaseOptInt(2, 0);
|
||||||
|
ptr->a = (uint8_t)moduleBaseOptInt(3, 255);
|
||||||
|
jerry_object_set_native_ptr(
|
||||||
|
callInfo->this_value, &MODULE_COLOR_PROTO.info, ptr
|
||||||
|
);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleColorGetR) {
|
||||||
|
color_t *c = moduleColorFrom(callInfo->this_value);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
return jerry_number((double)c->r);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleColorSetR) {
|
||||||
|
moduleBaseRequireArgs(1);
|
||||||
|
color_t *c = moduleColorFrom(callInfo->this_value);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
c->r = (uint8_t)moduleBaseArgInt(0);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleColorGetG) {
|
||||||
|
color_t *c = moduleColorFrom(callInfo->this_value);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
return jerry_number((double)c->g);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleColorSetG) {
|
||||||
|
moduleBaseRequireArgs(1);
|
||||||
|
color_t *c = moduleColorFrom(callInfo->this_value);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
c->g = (uint8_t)moduleBaseArgInt(0);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleColorGetB) {
|
||||||
|
color_t *c = moduleColorFrom(callInfo->this_value);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
return jerry_number((double)c->b);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleColorSetB) {
|
||||||
|
moduleBaseRequireArgs(1);
|
||||||
|
color_t *c = moduleColorFrom(callInfo->this_value);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
c->b = (uint8_t)moduleBaseArgInt(0);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleColorGetA) {
|
||||||
|
color_t *c = moduleColorFrom(callInfo->this_value);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
return jerry_number((double)c->a);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleColorSetA) {
|
||||||
|
moduleBaseRequireArgs(1);
|
||||||
|
color_t *c = moduleColorFrom(callInfo->this_value);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
c->a = (uint8_t)moduleBaseArgInt(0);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleColorToString) {
|
||||||
|
color_t *c = moduleColorFrom(callInfo->this_value);
|
||||||
|
if(!c) return jerry_string_sz("Color:invalid");
|
||||||
|
char_t buf[32];
|
||||||
|
snprintf(buf, sizeof(buf), "Color(%u,%u,%u,%u)",
|
||||||
|
(unsigned)c->r, (unsigned)c->g,
|
||||||
|
(unsigned)c->b, (unsigned)c->a
|
||||||
|
);
|
||||||
|
return jerry_string_sz(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void moduleColorInit(void) {
|
||||||
|
scriptProtoInit(
|
||||||
|
&MODULE_COLOR_PROTO, "Color",
|
||||||
|
sizeof(color_t), moduleColorConstructor
|
||||||
|
);
|
||||||
|
|
||||||
|
scriptProtoDefineProp(&MODULE_COLOR_PROTO, "r", moduleColorGetR, moduleColorSetR);
|
||||||
|
scriptProtoDefineProp(&MODULE_COLOR_PROTO, "g", moduleColorGetG, moduleColorSetG);
|
||||||
|
scriptProtoDefineProp(&MODULE_COLOR_PROTO, "b", moduleColorGetB, moduleColorSetB);
|
||||||
|
scriptProtoDefineProp(&MODULE_COLOR_PROTO, "a", moduleColorGetA, moduleColorSetA);
|
||||||
|
scriptProtoDefineToString(&MODULE_COLOR_PROTO, moduleColorToString);
|
||||||
|
|
||||||
|
/* Static named color constants on the constructor. */
|
||||||
|
struct { const char_t *name; color_t val; } constants[] = {
|
||||||
|
{ "WHITE", COLOR_WHITE },
|
||||||
|
{ "BLACK", COLOR_BLACK },
|
||||||
|
{ "RED", COLOR_RED },
|
||||||
|
{ "GREEN", COLOR_GREEN },
|
||||||
|
{ "BLUE", COLOR_BLUE },
|
||||||
|
{ "YELLOW", COLOR_YELLOW },
|
||||||
|
{ "CYAN", COLOR_CYAN },
|
||||||
|
{ "MAGENTA", COLOR_MAGENTA },
|
||||||
|
{ "TRANSPARENT", COLOR_TRANSPARENT },
|
||||||
|
{ "GRAY", COLOR_GRAY },
|
||||||
|
{ "LIGHT_GRAY", COLOR_LIGHT_GRAY },
|
||||||
|
{ "DARK_GRAY", COLOR_DARK_GRAY },
|
||||||
|
{ "ORANGE", COLOR_ORANGE },
|
||||||
|
{ "PURPLE", COLOR_PURPLE },
|
||||||
|
{ "PINK", COLOR_PINK },
|
||||||
|
{ "TEAL", COLOR_TEAL },
|
||||||
|
{ "CORNFLOWER_BLUE", COLOR_CORNFLOWER_BLUE },
|
||||||
|
};
|
||||||
|
jerry_value_t ctor = MODULE_COLOR_PROTO.constructor;
|
||||||
|
for(int i = 0; i < (int)(sizeof(constants)/sizeof(constants[0])); i++) {
|
||||||
|
jerry_value_t k = jerry_string_sz(constants[i].name);
|
||||||
|
jerry_value_t v = moduleColorPush(constants[i].val);
|
||||||
|
jerry_object_set(ctor, k, v);
|
||||||
|
jerry_value_free(v);
|
||||||
|
jerry_value_free(k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void moduleColorDispose(void) {
|
||||||
|
scriptProtoDispose(&MODULE_COLOR_PROTO);
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "script/module/modulebase.h"
|
||||||
|
#include "script/scriptproto.h"
|
||||||
|
#include "asset/asset.h"
|
||||||
|
#include "asset/loader/assetloader.h"
|
||||||
|
#include "display/texture/texture.h"
|
||||||
|
#include "util/memory.h"
|
||||||
|
|
||||||
|
static scriptproto_t MODULE_TEXTURE_PROTO;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
assetentry_t *entry;
|
||||||
|
} jstexture_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom free callback — unlocks the asset entry so it can be reclaimed
|
||||||
|
* once the JS Texture object is garbage collected.
|
||||||
|
*/
|
||||||
|
static void moduleTextureFree(
|
||||||
|
void *ptr,
|
||||||
|
jerry_object_native_info_t *info
|
||||||
|
) {
|
||||||
|
(void)info;
|
||||||
|
jstexture_t *tex = (jstexture_t *)ptr;
|
||||||
|
if(tex && tex->entry) {
|
||||||
|
assetUnlockEntry(tex->entry);
|
||||||
|
tex->entry = NULL;
|
||||||
|
}
|
||||||
|
memoryFree(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleTextureCtor) {
|
||||||
|
(void)callInfo; (void)args; (void)argc;
|
||||||
|
return moduleBaseThrow("Texture cannot be instantiated with new");
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline jstexture_t *moduleTextureSelf(
|
||||||
|
const jerry_call_info_t *callInfo
|
||||||
|
) {
|
||||||
|
return (jstexture_t *)scriptProtoGetValue(
|
||||||
|
&MODULE_TEXTURE_PROTO, callInfo->this_value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleTextureGetWidth) {
|
||||||
|
jstexture_t *t = moduleTextureSelf(callInfo);
|
||||||
|
if(!t || !t->entry) return jerry_undefined();
|
||||||
|
return jerry_number((double)t->entry->data.texture.width);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleTextureGetHeight) {
|
||||||
|
jstexture_t *t = moduleTextureSelf(callInfo);
|
||||||
|
if(!t || !t->entry) return jerry_undefined();
|
||||||
|
return jerry_number((double)t->entry->data.texture.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleTextureToString) {
|
||||||
|
jstexture_t *t = moduleTextureSelf(callInfo);
|
||||||
|
if(!t || !t->entry) return jerry_string_sz("Texture:invalid");
|
||||||
|
char_t buf[64];
|
||||||
|
snprintf(buf, sizeof(buf), "Texture(%dx%d)",
|
||||||
|
t->entry->data.texture.width,
|
||||||
|
t->entry->data.texture.height
|
||||||
|
);
|
||||||
|
return jerry_string_sz(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void moduleTextureInit(void) {
|
||||||
|
scriptProtoInit(
|
||||||
|
&MODULE_TEXTURE_PROTO, "Texture",
|
||||||
|
sizeof(jstexture_t), moduleTextureCtor
|
||||||
|
);
|
||||||
|
/* Override the default free callback so the asset lock is released on GC. */
|
||||||
|
MODULE_TEXTURE_PROTO.info.free_cb = moduleTextureFree;
|
||||||
|
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_TEXTURE_PROTO, "width", moduleTextureGetWidth, NULL
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_TEXTURE_PROTO, "height", moduleTextureGetHeight, NULL
|
||||||
|
);
|
||||||
|
scriptProtoDefineToString(&MODULE_TEXTURE_PROTO, moduleTextureToString);
|
||||||
|
|
||||||
|
/* Texture.FORMAT_* constants */
|
||||||
|
jerry_value_t ctor = MODULE_TEXTURE_PROTO.constructor;
|
||||||
|
struct { const char_t *name; int val; } formats[] = {
|
||||||
|
{ "FORMAT_RGBA", TEXTURE_FORMAT_RGBA },
|
||||||
|
{ "FORMAT_PALETTE", TEXTURE_FORMAT_PALETTE },
|
||||||
|
};
|
||||||
|
for(int i = 0; i < 2; i++) {
|
||||||
|
jerry_value_t k = jerry_string_sz(formats[i].name);
|
||||||
|
jerry_value_t v = jerry_number((double)formats[i].val);
|
||||||
|
jerry_object_set(ctor, k, v);
|
||||||
|
jerry_value_free(v);
|
||||||
|
jerry_value_free(k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void moduleTextureDispose(void) {
|
||||||
|
scriptProtoDispose(&MODULE_TEXTURE_PROTO);
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "script/module/modulebase.h"
|
||||||
|
|
||||||
|
#define MODULE_FRAME_PENDING_MAX 64
|
||||||
|
|
||||||
|
static jerry_value_t MODULE_FRAME_PENDING[MODULE_FRAME_PENDING_MAX];
|
||||||
|
static uint32_t MODULE_FRAME_PENDING_COUNT = 0;
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleFrameFrame) {
|
||||||
|
if(MODULE_FRAME_PENDING_COUNT >= MODULE_FRAME_PENDING_MAX) {
|
||||||
|
return moduleBaseThrow("Too many pending frame() calls");
|
||||||
|
}
|
||||||
|
jerry_value_t promise = jerry_promise();
|
||||||
|
MODULE_FRAME_PENDING[MODULE_FRAME_PENDING_COUNT++] = jerry_value_copy(promise);
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void moduleFrameFlush(void) {
|
||||||
|
uint32_t count = MODULE_FRAME_PENDING_COUNT;
|
||||||
|
MODULE_FRAME_PENDING_COUNT = 0;
|
||||||
|
for(uint32_t i = 0; i < count; i++) {
|
||||||
|
jerry_value_t ret = jerry_promise_resolve(MODULE_FRAME_PENDING[i], jerry_undefined());
|
||||||
|
jerry_value_free(ret);
|
||||||
|
jerry_value_free(MODULE_FRAME_PENDING[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void moduleFrameInit(void) {
|
||||||
|
MODULE_FRAME_PENDING_COUNT = 0;
|
||||||
|
moduleBaseDefineGlobalMethod("frame", moduleFrameFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void moduleFrameDispose(void) {
|
||||||
|
for(uint32_t i = 0; i < MODULE_FRAME_PENDING_COUNT; i++) {
|
||||||
|
jerry_value_free(MODULE_FRAME_PENDING[i]);
|
||||||
|
}
|
||||||
|
MODULE_FRAME_PENDING_COUNT = 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "script/module/modulebase.h"
|
||||||
|
#include "time/time.h"
|
||||||
|
|
||||||
|
#define MODULE_TIMEOUT_PENDING_MAX 64
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
jerry_value_t promise;
|
||||||
|
float_t targetTime;
|
||||||
|
} moduletimeoutentry_t;
|
||||||
|
|
||||||
|
static moduletimeoutentry_t MODULE_TIMEOUT_PENDING[MODULE_TIMEOUT_PENDING_MAX];
|
||||||
|
static uint32_t MODULE_TIMEOUT_PENDING_COUNT = 0;
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleTimeoutTimeout) {
|
||||||
|
moduleBaseRequireArgs(1);
|
||||||
|
moduleBaseRequireNumber(0);
|
||||||
|
|
||||||
|
if(MODULE_TIMEOUT_PENDING_COUNT >= MODULE_TIMEOUT_PENDING_MAX) {
|
||||||
|
return moduleBaseThrow("Too many pending timeout() calls");
|
||||||
|
}
|
||||||
|
|
||||||
|
float_t ms = moduleBaseArgFloat(0);
|
||||||
|
jerry_value_t promise = jerry_promise();
|
||||||
|
MODULE_TIMEOUT_PENDING[MODULE_TIMEOUT_PENDING_COUNT].promise = jerry_value_copy(promise);
|
||||||
|
MODULE_TIMEOUT_PENDING[MODULE_TIMEOUT_PENDING_COUNT].targetTime = TIME.time + ms / 1000.0f;
|
||||||
|
MODULE_TIMEOUT_PENDING_COUNT++;
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void moduleTimeoutFlush(void) {
|
||||||
|
uint32_t i = 0;
|
||||||
|
while(i < MODULE_TIMEOUT_PENDING_COUNT) {
|
||||||
|
if(TIME.time >= MODULE_TIMEOUT_PENDING[i].targetTime) {
|
||||||
|
jerry_value_t ret = jerry_promise_resolve(
|
||||||
|
MODULE_TIMEOUT_PENDING[i].promise, jerry_undefined()
|
||||||
|
);
|
||||||
|
jerry_value_free(ret);
|
||||||
|
jerry_value_free(MODULE_TIMEOUT_PENDING[i].promise);
|
||||||
|
MODULE_TIMEOUT_PENDING_COUNT--;
|
||||||
|
if(i < MODULE_TIMEOUT_PENDING_COUNT) {
|
||||||
|
MODULE_TIMEOUT_PENDING[i] = MODULE_TIMEOUT_PENDING[MODULE_TIMEOUT_PENDING_COUNT];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void moduleTimeoutInit(void) {
|
||||||
|
MODULE_TIMEOUT_PENDING_COUNT = 0;
|
||||||
|
moduleBaseDefineGlobalMethod("timeout", moduleTimeoutTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void moduleTimeoutDispose(void) {
|
||||||
|
for(uint32_t i = 0; i < MODULE_TIMEOUT_PENDING_COUNT; i++) {
|
||||||
|
jerry_value_free(MODULE_TIMEOUT_PENDING[i].promise);
|
||||||
|
}
|
||||||
|
MODULE_TIMEOUT_PENDING_COUNT = 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "script/module/modulebase.h"
|
||||||
|
#include "script/scriptproto.h"
|
||||||
|
#include "script/module/entity/modulecomponent.h"
|
||||||
|
#include "entity/component/display/entitycamera.h"
|
||||||
|
|
||||||
|
static scriptproto_t MODULE_CAMERA_PROTO;
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleCameraCtor) {
|
||||||
|
(void)callInfo; (void)args; (void)argc;
|
||||||
|
return moduleBaseThrow("Camera cannot be instantiated with new");
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline jscomponent_t *moduleCameraSelf(
|
||||||
|
const jerry_call_info_t *callInfo
|
||||||
|
) {
|
||||||
|
return (jscomponent_t *)scriptProtoGetValue(
|
||||||
|
&MODULE_CAMERA_PROTO, callInfo->this_value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline entitycamera_t *moduleCameraData(const jscomponent_t *c) {
|
||||||
|
return (entitycamera_t *)componentGetData(
|
||||||
|
c->entityId, c->componentId, COMPONENT_TYPE_CAMERA
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleCameraGetEntity) {
|
||||||
|
jscomponent_t *c = moduleCameraSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
return jerry_number((double)c->entityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleCameraGetId) {
|
||||||
|
jscomponent_t *c = moduleCameraSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
return jerry_number((double)c->componentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleCameraGetFov) {
|
||||||
|
jscomponent_t *c = moduleCameraSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
entitycamera_t *cam = moduleCameraData(c);
|
||||||
|
if(!cam) return jerry_undefined();
|
||||||
|
return jerry_number((double)cam->perspective.fov);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleCameraSetFov) {
|
||||||
|
moduleBaseRequireArgs(1);
|
||||||
|
jscomponent_t *c = moduleCameraSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
entitycamera_t *cam = moduleCameraData(c);
|
||||||
|
if(!cam) return jerry_undefined();
|
||||||
|
cam->perspective.fov = moduleBaseArgFloat(0);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleCameraGetNearClip) {
|
||||||
|
jscomponent_t *c = moduleCameraSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
entitycamera_t *cam = moduleCameraData(c);
|
||||||
|
if(!cam) return jerry_undefined();
|
||||||
|
return jerry_number((double)cam->nearClip);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleCameraSetNearClip) {
|
||||||
|
moduleBaseRequireArgs(1);
|
||||||
|
jscomponent_t *c = moduleCameraSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
entitycamera_t *cam = moduleCameraData(c);
|
||||||
|
if(!cam) return jerry_undefined();
|
||||||
|
cam->nearClip = moduleBaseArgFloat(0);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleCameraGetFarClip) {
|
||||||
|
jscomponent_t *c = moduleCameraSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
entitycamera_t *cam = moduleCameraData(c);
|
||||||
|
if(!cam) return jerry_undefined();
|
||||||
|
return jerry_number((double)cam->farClip);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleCameraSetFarClip) {
|
||||||
|
moduleBaseRequireArgs(1);
|
||||||
|
jscomponent_t *c = moduleCameraSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
entitycamera_t *cam = moduleCameraData(c);
|
||||||
|
if(!cam) return jerry_undefined();
|
||||||
|
cam->farClip = moduleBaseArgFloat(0);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleCameraGetProjType) {
|
||||||
|
jscomponent_t *c = moduleCameraSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
entitycamera_t *cam = moduleCameraData(c);
|
||||||
|
if(!cam) return jerry_undefined();
|
||||||
|
return jerry_number((double)cam->projType);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleCameraSetProjType) {
|
||||||
|
moduleBaseRequireArgs(1);
|
||||||
|
jscomponent_t *c = moduleCameraSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
entitycamera_t *cam = moduleCameraData(c);
|
||||||
|
if(!cam) return jerry_undefined();
|
||||||
|
cam->projType = (entitycameraprojectiontype_t)moduleBaseArgInt(0);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleCameraToString) {
|
||||||
|
jscomponent_t *c = moduleCameraSelf(callInfo);
|
||||||
|
if(!c) return jerry_string_sz("Camera:invalid");
|
||||||
|
char_t buf[32];
|
||||||
|
snprintf(buf, sizeof(buf), "Camera(%u)", (unsigned)c->componentId);
|
||||||
|
return jerry_string_sz(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void moduleCameraInit(void) {
|
||||||
|
scriptProtoInit(
|
||||||
|
&MODULE_CAMERA_PROTO, "Camera",
|
||||||
|
sizeof(jscomponent_t), moduleCameraCtor
|
||||||
|
);
|
||||||
|
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_CAMERA_PROTO, "entity", moduleCameraGetEntity, NULL
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_CAMERA_PROTO, "id", moduleCameraGetId, NULL
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_CAMERA_PROTO, "fov", moduleCameraGetFov, moduleCameraSetFov
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_CAMERA_PROTO, "nearClip",
|
||||||
|
moduleCameraGetNearClip, moduleCameraSetNearClip
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_CAMERA_PROTO, "farClip",
|
||||||
|
moduleCameraGetFarClip, moduleCameraSetFarClip
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_CAMERA_PROTO, "projType",
|
||||||
|
moduleCameraGetProjType, moduleCameraSetProjType
|
||||||
|
);
|
||||||
|
scriptProtoDefineToString(&MODULE_CAMERA_PROTO, moduleCameraToString);
|
||||||
|
|
||||||
|
/* Camera.PERSPECTIVE, Camera.PERSPECTIVE_FLIPPED, Camera.ORTHOGRAPHIC */
|
||||||
|
jerry_value_t ctor = MODULE_CAMERA_PROTO.constructor;
|
||||||
|
struct { const char_t *name; int val; } projtypes[] = {
|
||||||
|
{ "PERSPECTIVE", ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE },
|
||||||
|
{ "PERSPECTIVE_FLIPPED", ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED },
|
||||||
|
{ "ORTHOGRAPHIC", ENTITY_CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC },
|
||||||
|
};
|
||||||
|
for(int i = 0; i < 3; i++) {
|
||||||
|
jerry_value_t k = jerry_string_sz(projtypes[i].name);
|
||||||
|
jerry_value_t v = jerry_number((double)projtypes[i].val);
|
||||||
|
jerry_object_set(ctor, k, v);
|
||||||
|
jerry_value_free(v);
|
||||||
|
jerry_value_free(k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void moduleCameraDispose(void) {
|
||||||
|
scriptProtoDispose(&MODULE_CAMERA_PROTO);
|
||||||
|
}
|
||||||
@@ -0,0 +1,248 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "script/module/modulebase.h"
|
||||||
|
#include "script/scriptproto.h"
|
||||||
|
#include "script/module/math/modulevec3.h"
|
||||||
|
#include "script/module/entity/modulecomponent.h"
|
||||||
|
#include "entity/component/display/entityposition.h"
|
||||||
|
|
||||||
|
static scriptproto_t MODULE_POSITION_PROTO;
|
||||||
|
|
||||||
|
moduleBaseFunction(modulePositionCtor) {
|
||||||
|
(void)callInfo; (void)args; (void)argc;
|
||||||
|
return moduleBaseThrow("Position cannot be instantiated with new");
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline jscomponent_t *modulePositionSelf(
|
||||||
|
const jerry_call_info_t *callInfo
|
||||||
|
) {
|
||||||
|
return (jscomponent_t *)scriptProtoGetValue(
|
||||||
|
&MODULE_POSITION_PROTO, callInfo->this_value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(modulePositionGetEntity) {
|
||||||
|
jscomponent_t *c = modulePositionSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
return jerry_number((double)c->entityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(modulePositionGetId) {
|
||||||
|
jscomponent_t *c = modulePositionSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
return jerry_number((double)c->componentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(modulePositionGetLocalPos) {
|
||||||
|
jscomponent_t *c = modulePositionSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
vec3 v;
|
||||||
|
entityPositionGetLocalPosition(c->entityId, c->componentId, v);
|
||||||
|
return moduleVec3Push(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(modulePositionSetLocalPos) {
|
||||||
|
moduleBaseRequireArgs(1);
|
||||||
|
jscomponent_t *c = modulePositionSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
float_t *v = moduleVec3From(args[0]);
|
||||||
|
if(!v) return moduleBaseThrow("Position.localPosition: expected Vec3");
|
||||||
|
entityPositionSetLocalPosition(c->entityId, c->componentId, v);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(modulePositionGetWorldPos) {
|
||||||
|
jscomponent_t *c = modulePositionSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
vec3 v;
|
||||||
|
entityPositionGetWorldPosition(c->entityId, c->componentId, v);
|
||||||
|
return moduleVec3Push(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(modulePositionSetWorldPos) {
|
||||||
|
moduleBaseRequireArgs(1);
|
||||||
|
jscomponent_t *c = modulePositionSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
float_t *v = moduleVec3From(args[0]);
|
||||||
|
if(!v) return moduleBaseThrow("Position.worldPosition: expected Vec3");
|
||||||
|
entityPositionSetWorldPosition(c->entityId, c->componentId, v);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(modulePositionGetLocalRot) {
|
||||||
|
jscomponent_t *c = modulePositionSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
vec3 v;
|
||||||
|
entityPositionGetLocalRotation(c->entityId, c->componentId, v);
|
||||||
|
return moduleVec3Push(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(modulePositionSetLocalRot) {
|
||||||
|
moduleBaseRequireArgs(1);
|
||||||
|
jscomponent_t *c = modulePositionSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
float_t *v = moduleVec3From(args[0]);
|
||||||
|
if(!v) return moduleBaseThrow("Position.localRotation: expected Vec3");
|
||||||
|
entityPositionSetLocalRotation(c->entityId, c->componentId, v);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(modulePositionGetWorldRot) {
|
||||||
|
jscomponent_t *c = modulePositionSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
vec3 v;
|
||||||
|
entityPositionGetWorldRotation(c->entityId, c->componentId, v);
|
||||||
|
return moduleVec3Push(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(modulePositionSetWorldRot) {
|
||||||
|
moduleBaseRequireArgs(1);
|
||||||
|
jscomponent_t *c = modulePositionSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
float_t *v = moduleVec3From(args[0]);
|
||||||
|
if(!v) return moduleBaseThrow("Position.worldRotation: expected Vec3");
|
||||||
|
entityPositionSetWorldRotation(c->entityId, c->componentId, v);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(modulePositionGetLocalScale) {
|
||||||
|
jscomponent_t *c = modulePositionSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
vec3 v;
|
||||||
|
entityPositionGetLocalScale(c->entityId, c->componentId, v);
|
||||||
|
return moduleVec3Push(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(modulePositionSetLocalScale) {
|
||||||
|
moduleBaseRequireArgs(1);
|
||||||
|
jscomponent_t *c = modulePositionSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
float_t *v = moduleVec3From(args[0]);
|
||||||
|
if(!v) return moduleBaseThrow("Position.localScale: expected Vec3");
|
||||||
|
entityPositionSetLocalScale(c->entityId, c->componentId, v);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(modulePositionGetWorldScale) {
|
||||||
|
jscomponent_t *c = modulePositionSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
vec3 v;
|
||||||
|
entityPositionGetWorldScale(c->entityId, c->componentId, v);
|
||||||
|
return moduleVec3Push(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(modulePositionSetWorldScale) {
|
||||||
|
moduleBaseRequireArgs(1);
|
||||||
|
jscomponent_t *c = modulePositionSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
float_t *v = moduleVec3From(args[0]);
|
||||||
|
if(!v) return moduleBaseThrow("Position.worldScale: expected Vec3");
|
||||||
|
entityPositionSetWorldScale(c->entityId, c->componentId, v);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(modulePositionLookAt) {
|
||||||
|
jscomponent_t *c = modulePositionSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
moduleBaseRequireArgs(1);
|
||||||
|
float_t *target = moduleVec3From(args[0]);
|
||||||
|
if(!target) return moduleBaseThrow("Position.lookAt: expected Vec3 target");
|
||||||
|
|
||||||
|
vec3 eye;
|
||||||
|
entityPositionGetLocalPosition(c->entityId, c->componentId, eye);
|
||||||
|
|
||||||
|
vec3 up = { 0.0f, 1.0f, 0.0f };
|
||||||
|
if(argc >= 2) {
|
||||||
|
float_t *upArg = moduleVec3From(args[1]);
|
||||||
|
if(upArg) glm_vec3_copy(upArg, up);
|
||||||
|
}
|
||||||
|
|
||||||
|
entityPositionLookAt(c->entityId, c->componentId, eye, target, up);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(modulePositionSetParent) {
|
||||||
|
jscomponent_t *c = modulePositionSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
if(argc == 0 ||
|
||||||
|
jerry_value_is_null(args[0]) ||
|
||||||
|
jerry_value_is_undefined(args[0])) {
|
||||||
|
entityPositionSetParent(
|
||||||
|
c->entityId, c->componentId,
|
||||||
|
ENTITY_ID_INVALID, COMPONENT_ID_INVALID
|
||||||
|
);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
jscomponent_t *parent = (jscomponent_t *)scriptProtoGetValue(
|
||||||
|
&MODULE_POSITION_PROTO, args[0]
|
||||||
|
);
|
||||||
|
if(!parent) return moduleBaseThrow("Position.setParent: expected Position or null");
|
||||||
|
entityPositionSetParent(
|
||||||
|
c->entityId, c->componentId,
|
||||||
|
parent->entityId, parent->componentId
|
||||||
|
);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(modulePositionToString) {
|
||||||
|
jscomponent_t *c = modulePositionSelf(callInfo);
|
||||||
|
if(!c) return jerry_string_sz("Position:invalid");
|
||||||
|
char_t buf[32];
|
||||||
|
snprintf(buf, sizeof(buf), "Position(%u)", (unsigned)c->componentId);
|
||||||
|
return jerry_string_sz(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void modulePositionInit(void) {
|
||||||
|
scriptProtoInit(
|
||||||
|
&MODULE_POSITION_PROTO, "Position",
|
||||||
|
sizeof(jscomponent_t), modulePositionCtor
|
||||||
|
);
|
||||||
|
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_POSITION_PROTO, "entity", modulePositionGetEntity, NULL
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_POSITION_PROTO, "id", modulePositionGetId, NULL
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_POSITION_PROTO, "localPosition",
|
||||||
|
modulePositionGetLocalPos, modulePositionSetLocalPos
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_POSITION_PROTO, "worldPosition",
|
||||||
|
modulePositionGetWorldPos, modulePositionSetWorldPos
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_POSITION_PROTO, "localRotation",
|
||||||
|
modulePositionGetLocalRot, modulePositionSetLocalRot
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_POSITION_PROTO, "worldRotation",
|
||||||
|
modulePositionGetWorldRot, modulePositionSetWorldRot
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_POSITION_PROTO, "localScale",
|
||||||
|
modulePositionGetLocalScale, modulePositionSetLocalScale
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_POSITION_PROTO, "worldScale",
|
||||||
|
modulePositionGetWorldScale, modulePositionSetWorldScale
|
||||||
|
);
|
||||||
|
scriptProtoDefineFunc(
|
||||||
|
&MODULE_POSITION_PROTO, "lookAt", modulePositionLookAt
|
||||||
|
);
|
||||||
|
scriptProtoDefineFunc(
|
||||||
|
&MODULE_POSITION_PROTO, "setParent", modulePositionSetParent
|
||||||
|
);
|
||||||
|
scriptProtoDefineToString(&MODULE_POSITION_PROTO, modulePositionToString);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void modulePositionDispose(void) {
|
||||||
|
scriptProtoDispose(&MODULE_POSITION_PROTO);
|
||||||
|
}
|
||||||
@@ -0,0 +1,303 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "script/module/modulebase.h"
|
||||||
|
#include "script/module/display/modulecolor.h"
|
||||||
|
#include "script/module/display/moduletexture.h"
|
||||||
|
#include "script/module/entity/modulecomponent.h"
|
||||||
|
#include "script/scriptproto.h"
|
||||||
|
#include "entity/component/display/entityrenderable.h"
|
||||||
|
|
||||||
|
static scriptproto_t MODULE_RENDERABLE_PROTO;
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleRenderableCtor) {
|
||||||
|
(void)callInfo; (void)args; (void)argc;
|
||||||
|
return moduleBaseThrow("Renderable cannot be instantiated with new");
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline jscomponent_t *moduleRenderableSelf(
|
||||||
|
const jerry_call_info_t *callInfo
|
||||||
|
) {
|
||||||
|
return (jscomponent_t *)scriptProtoGetValue(
|
||||||
|
&MODULE_RENDERABLE_PROTO, callInfo->this_value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline entityrenderable_t *moduleRenderableData(const jscomponent_t *c) {
|
||||||
|
return (entityrenderable_t *)componentGetData(
|
||||||
|
c->entityId, c->componentId, COMPONENT_TYPE_RENDERABLE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Read a float from a JS array at index, returning def if out of range. */
|
||||||
|
static inline float_t moduleRenderableArrayFloat(
|
||||||
|
const jerry_value_t arr,
|
||||||
|
const uint32_t idx,
|
||||||
|
const float_t def
|
||||||
|
) {
|
||||||
|
if(idx >= jerry_array_length(arr)) return def;
|
||||||
|
jerry_value_t v = jerry_object_get_index(arr, idx);
|
||||||
|
float_t f = jerry_value_is_number(v) ? (float_t)jerry_value_as_number(v) : def;
|
||||||
|
jerry_value_free(v);
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleRenderableGetEntity) {
|
||||||
|
jscomponent_t *c = moduleRenderableSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
return jerry_number((double)c->entityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleRenderableGetId) {
|
||||||
|
jscomponent_t *c = moduleRenderableSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
return jerry_number((double)c->componentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleRenderableGetType) {
|
||||||
|
jscomponent_t *c = moduleRenderableSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
entityrenderable_t *r = moduleRenderableData(c);
|
||||||
|
if(!r) return jerry_undefined();
|
||||||
|
return jerry_number((double)r->type);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleRenderableSetType) {
|
||||||
|
moduleBaseRequireArgs(1);
|
||||||
|
jscomponent_t *c = moduleRenderableSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
entityRenderableSetType(
|
||||||
|
c->entityId, c->componentId,
|
||||||
|
(entityrenderabletype_t)moduleBaseArgInt(0)
|
||||||
|
);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleRenderableGetPriority) {
|
||||||
|
jscomponent_t *c = moduleRenderableSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
entityrenderable_t *r = moduleRenderableData(c);
|
||||||
|
if(!r) return jerry_undefined();
|
||||||
|
return jerry_number((double)r->priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleRenderableSetPriority) {
|
||||||
|
moduleBaseRequireArgs(1);
|
||||||
|
jscomponent_t *c = moduleRenderableSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
entityRenderableSetPriority(
|
||||||
|
c->entityId, c->componentId,
|
||||||
|
(int8_t)moduleBaseArgInt(0)
|
||||||
|
);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleRenderableGetColor) {
|
||||||
|
jscomponent_t *c = moduleRenderableSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
entityrenderable_t *r = moduleRenderableData(c);
|
||||||
|
if(!r) return jerry_undefined();
|
||||||
|
return moduleColorPush(r->data.material.material.unlit.color);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleRenderableSetColor) {
|
||||||
|
moduleBaseRequireArgs(1);
|
||||||
|
jscomponent_t *c = moduleRenderableSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
entityrenderable_t *r = moduleRenderableData(c);
|
||||||
|
if(!r) return jerry_undefined();
|
||||||
|
color_t *col = moduleColorFrom(args[0]);
|
||||||
|
if(!col) return moduleBaseThrow("Renderable.color: expected Color");
|
||||||
|
r->data.material.material.unlit.color = *col;
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* texture getter — returns the pinned Texture instance, or undefined if none.
|
||||||
|
*/
|
||||||
|
moduleBaseFunction(moduleRenderableGetTexture) {
|
||||||
|
jscomponent_t *c = moduleRenderableSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
jerry_value_t key = jerry_string_sz("_tex");
|
||||||
|
jerry_value_t val = jerry_object_get(callInfo->this_value, key);
|
||||||
|
jerry_value_free(key);
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* texture setter — switches to SPRITEBATCH, binds the texture, and pins the
|
||||||
|
* Texture JS object so GC won't free the asset while the pointer is live.
|
||||||
|
*/
|
||||||
|
moduleBaseFunction(moduleRenderableSetTexture) {
|
||||||
|
moduleBaseRequireArgs(1);
|
||||||
|
jscomponent_t *c = moduleRenderableSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
entityrenderable_t *r = moduleRenderableData(c);
|
||||||
|
if(!r) return jerry_undefined();
|
||||||
|
jstexture_t *tex = (jstexture_t *)scriptProtoGetValue(
|
||||||
|
&MODULE_TEXTURE_PROTO, args[0]
|
||||||
|
);
|
||||||
|
if(!tex || !tex->entry) {
|
||||||
|
return moduleBaseThrow("Renderable.texture: expected Texture");
|
||||||
|
}
|
||||||
|
r->type = ENTITY_RENDERABLE_TYPE_SPRITEBATCH;
|
||||||
|
r->data.spritebatch.texture = &tex->entry->data.texture;
|
||||||
|
jerry_value_t pinKey = jerry_string_sz("_tex");
|
||||||
|
jerry_object_set(callInfo->this_value, pinKey, args[0]);
|
||||||
|
jerry_value_free(pinKey);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* sprites getter — returns a JS array of sprite sub-arrays.
|
||||||
|
* Each element is [x1,y1,z1, x2,y2,z2, u1,v1, u2,v2] (10 numbers).
|
||||||
|
*/
|
||||||
|
moduleBaseFunction(moduleRenderableGetSprites) {
|
||||||
|
jscomponent_t *c = moduleRenderableSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
entityrenderable_t *r = moduleRenderableData(c);
|
||||||
|
if(!r) return jerry_undefined();
|
||||||
|
const entityrenderablespritebatch_t *sb = &r->data.spritebatch;
|
||||||
|
|
||||||
|
jerry_value_t arr = jerry_array((uint32_t)sb->spriteCount);
|
||||||
|
for(uint32_t i = 0; i < (uint32_t)sb->spriteCount; i++) {
|
||||||
|
const spritebatchsprite_t *s = &sb->sprites[i];
|
||||||
|
float_t vals[10] = {
|
||||||
|
s->min[0], s->min[1], s->min[2],
|
||||||
|
s->max[0], s->max[1], s->max[2],
|
||||||
|
s->uvMin[0], s->uvMin[1],
|
||||||
|
s->uvMax[0], s->uvMax[1],
|
||||||
|
};
|
||||||
|
jerry_value_t sprite = jerry_array(10);
|
||||||
|
for(uint32_t j = 0; j < 10; j++) {
|
||||||
|
jerry_value_t num = jerry_number((double)vals[j]);
|
||||||
|
jerry_object_set_index(sprite, j, num);
|
||||||
|
jerry_value_free(num);
|
||||||
|
}
|
||||||
|
jerry_object_set_index(arr, i, sprite);
|
||||||
|
jerry_value_free(sprite);
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* sprites setter — accepts an array of sub-arrays.
|
||||||
|
* Each element: 10 numbers (3D) or 8 numbers (2D, z defaults to 0).
|
||||||
|
*/
|
||||||
|
moduleBaseFunction(moduleRenderableSetSprites) {
|
||||||
|
moduleBaseRequireArgs(1);
|
||||||
|
jscomponent_t *c = moduleRenderableSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
entityrenderable_t *r = moduleRenderableData(c);
|
||||||
|
if(!r) return jerry_undefined();
|
||||||
|
if(!jerry_value_is_array(args[0])) {
|
||||||
|
return moduleBaseThrow("Renderable.sprites: expected Array");
|
||||||
|
}
|
||||||
|
entityrenderablespritebatch_t *sb = &r->data.spritebatch;
|
||||||
|
uint32_t count = jerry_array_length(args[0]);
|
||||||
|
if(count > ENTITY_RENDERABLE_SPRITEBATCH_SPRITES_MAX) {
|
||||||
|
return moduleBaseThrow("Renderable.sprites: exceeds sprite capacity");
|
||||||
|
}
|
||||||
|
sb->spriteCount = 0;
|
||||||
|
for(uint32_t i = 0; i < count; i++) {
|
||||||
|
jerry_value_t elem = jerry_object_get_index(args[0], i);
|
||||||
|
if(!jerry_value_is_array(elem)) {
|
||||||
|
jerry_value_free(elem);
|
||||||
|
return moduleBaseThrow("Renderable.sprites: each element must be an Array");
|
||||||
|
}
|
||||||
|
spritebatchsprite_t s;
|
||||||
|
if(jerry_array_length(elem) >= 10) {
|
||||||
|
s.min[0] = moduleRenderableArrayFloat(elem, 0, 0.0f);
|
||||||
|
s.min[1] = moduleRenderableArrayFloat(elem, 1, 0.0f);
|
||||||
|
s.min[2] = moduleRenderableArrayFloat(elem, 2, 0.0f);
|
||||||
|
s.max[0] = moduleRenderableArrayFloat(elem, 3, 0.0f);
|
||||||
|
s.max[1] = moduleRenderableArrayFloat(elem, 4, 0.0f);
|
||||||
|
s.max[2] = moduleRenderableArrayFloat(elem, 5, 0.0f);
|
||||||
|
s.uvMin[0] = moduleRenderableArrayFloat(elem, 6, 0.0f);
|
||||||
|
s.uvMin[1] = moduleRenderableArrayFloat(elem, 7, 0.0f);
|
||||||
|
s.uvMax[0] = moduleRenderableArrayFloat(elem, 8, 1.0f);
|
||||||
|
s.uvMax[1] = moduleRenderableArrayFloat(elem, 9, 1.0f);
|
||||||
|
} else {
|
||||||
|
s.min[0] = moduleRenderableArrayFloat(elem, 0, 0.0f);
|
||||||
|
s.min[1] = moduleRenderableArrayFloat(elem, 1, 0.0f);
|
||||||
|
s.min[2] = 0.0f;
|
||||||
|
s.max[0] = moduleRenderableArrayFloat(elem, 2, 0.0f);
|
||||||
|
s.max[1] = moduleRenderableArrayFloat(elem, 3, 0.0f);
|
||||||
|
s.max[2] = 0.0f;
|
||||||
|
s.uvMin[0] = moduleRenderableArrayFloat(elem, 4, 0.0f);
|
||||||
|
s.uvMin[1] = moduleRenderableArrayFloat(elem, 5, 0.0f);
|
||||||
|
s.uvMax[0] = moduleRenderableArrayFloat(elem, 6, 1.0f);
|
||||||
|
s.uvMax[1] = moduleRenderableArrayFloat(elem, 7, 1.0f);
|
||||||
|
}
|
||||||
|
jerry_value_free(elem);
|
||||||
|
sb->sprites[sb->spriteCount++] = s;
|
||||||
|
}
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleRenderableToString) {
|
||||||
|
jscomponent_t *c = moduleRenderableSelf(callInfo);
|
||||||
|
if(!c) return jerry_string_sz("Renderable:invalid");
|
||||||
|
char_t buf[32];
|
||||||
|
snprintf(buf, sizeof(buf), "Renderable(%u)", (unsigned)c->componentId);
|
||||||
|
return jerry_string_sz(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void moduleRenderableInit(void) {
|
||||||
|
scriptProtoInit(
|
||||||
|
&MODULE_RENDERABLE_PROTO, "Renderable",
|
||||||
|
sizeof(jscomponent_t), moduleRenderableCtor
|
||||||
|
);
|
||||||
|
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_RENDERABLE_PROTO, "entity", moduleRenderableGetEntity, NULL
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_RENDERABLE_PROTO, "id", moduleRenderableGetId, NULL
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_RENDERABLE_PROTO, "type",
|
||||||
|
moduleRenderableGetType, moduleRenderableSetType
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_RENDERABLE_PROTO, "priority",
|
||||||
|
moduleRenderableGetPriority, moduleRenderableSetPriority
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_RENDERABLE_PROTO, "color",
|
||||||
|
moduleRenderableGetColor, moduleRenderableSetColor
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_RENDERABLE_PROTO, "texture",
|
||||||
|
moduleRenderableGetTexture, moduleRenderableSetTexture
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_RENDERABLE_PROTO, "sprites",
|
||||||
|
moduleRenderableGetSprites, moduleRenderableSetSprites
|
||||||
|
);
|
||||||
|
scriptProtoDefineToString(&MODULE_RENDERABLE_PROTO, moduleRenderableToString);
|
||||||
|
|
||||||
|
/* Renderable.SHADER_MATERIAL, .SPRITEBATCH, .CUSTOM */
|
||||||
|
jerry_value_t ctor = MODULE_RENDERABLE_PROTO.constructor;
|
||||||
|
struct { const char_t *name; int val; } types[] = {
|
||||||
|
{ "SHADER_MATERIAL", ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL },
|
||||||
|
{ "SPRITEBATCH", ENTITY_RENDERABLE_TYPE_SPRITEBATCH },
|
||||||
|
{ "CUSTOM", ENTITY_RENDERABLE_TYPE_CUSTOM },
|
||||||
|
};
|
||||||
|
for(int i = 0; i < 3; i++) {
|
||||||
|
jerry_value_t k = jerry_string_sz(types[i].name);
|
||||||
|
jerry_value_t v = jerry_number((double)types[i].val);
|
||||||
|
jerry_object_set(ctor, k, v);
|
||||||
|
jerry_value_free(v);
|
||||||
|
jerry_value_free(k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void moduleRenderableDispose(void) {
|
||||||
|
scriptProtoDispose(&MODULE_RENDERABLE_PROTO);
|
||||||
|
}
|
||||||
@@ -6,11 +6,49 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "script/module/entity/modulecomponent.h"
|
||||||
|
#include "display/modulecamera.h"
|
||||||
|
#include "display/moduleposition.h"
|
||||||
|
#include "display/modulerenderable.h"
|
||||||
|
#include "physics/modulephysics.h"
|
||||||
|
#include "trigger/moduletrigger.h"
|
||||||
|
|
||||||
/* Include component modules here as they are added. */
|
/**
|
||||||
|
* Returns a typed JS instance for a newly-added component. Falls back to the
|
||||||
|
* generic Component proto for types that have no specific module yet.
|
||||||
|
*/
|
||||||
|
static jerry_value_t moduleComponentListCreateInstance(
|
||||||
|
const componenttype_t type,
|
||||||
|
const jscomponent_t *comp
|
||||||
|
) {
|
||||||
|
switch(type) {
|
||||||
|
case COMPONENT_TYPE_CAMERA:
|
||||||
|
return scriptProtoCreateValue(&MODULE_CAMERA_PROTO, comp);
|
||||||
|
case COMPONENT_TYPE_PHYSICS:
|
||||||
|
return scriptProtoCreateValue(&MODULE_PHYSICS_PROTO, comp);
|
||||||
|
case COMPONENT_TYPE_POSITION:
|
||||||
|
return scriptProtoCreateValue(&MODULE_POSITION_PROTO, comp);
|
||||||
|
case COMPONENT_TYPE_RENDERABLE:
|
||||||
|
return scriptProtoCreateValue(&MODULE_RENDERABLE_PROTO, comp);
|
||||||
|
case COMPONENT_TYPE_TRIGGER:
|
||||||
|
return scriptProtoCreateValue(&MODULE_TRIGGER_PROTO, comp);
|
||||||
|
default:
|
||||||
|
return scriptProtoCreateValue(&MODULE_COMPONENT_PROTO, comp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void moduleComponentListInit(void) {
|
static void moduleComponentListInit(void) {
|
||||||
|
moduleCameraInit();
|
||||||
|
modulePhysicsInit();
|
||||||
|
modulePositionInit();
|
||||||
|
moduleRenderableInit();
|
||||||
|
moduleTriggerInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void moduleComponentListDispose(void) {
|
static void moduleComponentListDispose(void) {
|
||||||
|
moduleTriggerDispose();
|
||||||
|
moduleRenderableDispose();
|
||||||
|
modulePositionDispose();
|
||||||
|
modulePhysicsDispose();
|
||||||
|
moduleCameraDispose();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,204 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "script/module/modulebase.h"
|
||||||
|
#include "script/scriptproto.h"
|
||||||
|
#include "script/module/math/modulevec3.h"
|
||||||
|
#include "script/module/entity/modulecomponent.h"
|
||||||
|
#include "entity/component/physics/entityphysics.h"
|
||||||
|
|
||||||
|
static scriptproto_t MODULE_PHYSICS_PROTO;
|
||||||
|
|
||||||
|
moduleBaseFunction(modulePhysicsCtor) {
|
||||||
|
(void)callInfo; (void)args; (void)argc;
|
||||||
|
return moduleBaseThrow("Physics cannot be instantiated with new");
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline jscomponent_t *modulePhysicsSelf(
|
||||||
|
const jerry_call_info_t *callInfo
|
||||||
|
) {
|
||||||
|
return (jscomponent_t *)scriptProtoGetValue(
|
||||||
|
&MODULE_PHYSICS_PROTO, callInfo->this_value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(modulePhysicsGetEntity) {
|
||||||
|
jscomponent_t *c = modulePhysicsSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
return jerry_number((double)c->entityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(modulePhysicsGetId) {
|
||||||
|
jscomponent_t *c = modulePhysicsSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
return jerry_number((double)c->componentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(modulePhysicsGetBodyType) {
|
||||||
|
jscomponent_t *c = modulePhysicsSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
return jerry_number((double)entityPhysicsGetBodyType(c->entityId, c->componentId));
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(modulePhysicsSetBodyType) {
|
||||||
|
moduleBaseRequireArgs(1);
|
||||||
|
jscomponent_t *c = modulePhysicsSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
entityPhysicsSetBodyType(
|
||||||
|
c->entityId, c->componentId,
|
||||||
|
(physicsbodytype_t)moduleBaseArgInt(0)
|
||||||
|
);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(modulePhysicsGetShape) {
|
||||||
|
jscomponent_t *c = modulePhysicsSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
return jerry_number((double)entityPhysicsGetShape(c->entityId, c->componentId).type);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(modulePhysicsSetShape) {
|
||||||
|
moduleBaseRequireArgs(1);
|
||||||
|
jscomponent_t *c = modulePhysicsSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
physicsshape_t shape = entityPhysicsGetShape(c->entityId, c->componentId);
|
||||||
|
shape.type = (physicshapetype_t)moduleBaseArgInt(0);
|
||||||
|
entityPhysicsSetShape(c->entityId, c->componentId, shape);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(modulePhysicsGetVelocity) {
|
||||||
|
jscomponent_t *c = modulePhysicsSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
vec3 v;
|
||||||
|
entityPhysicsGetVelocity(c->entityId, c->componentId, v);
|
||||||
|
return moduleVec3Push(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(modulePhysicsSetVelocity) {
|
||||||
|
moduleBaseRequireArgs(1);
|
||||||
|
jscomponent_t *c = modulePhysicsSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
float_t *v = moduleVec3From(args[0]);
|
||||||
|
if(!v) return moduleBaseThrow("Physics.velocity: expected Vec3");
|
||||||
|
entityPhysicsSetVelocity(c->entityId, c->componentId, v);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(modulePhysicsGetGravityScale) {
|
||||||
|
jscomponent_t *c = modulePhysicsSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
entityphysics_t *p = entityPhysicsGet(c->entityId, c->componentId);
|
||||||
|
if(!p) return jerry_undefined();
|
||||||
|
return jerry_number((double)p->gravityScale);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(modulePhysicsSetGravityScale) {
|
||||||
|
moduleBaseRequireArgs(1);
|
||||||
|
jscomponent_t *c = modulePhysicsSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
entityphysics_t *p = entityPhysicsGet(c->entityId, c->componentId);
|
||||||
|
if(!p) return jerry_undefined();
|
||||||
|
p->gravityScale = moduleBaseArgFloat(0);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(modulePhysicsGetOnGround) {
|
||||||
|
jscomponent_t *c = modulePhysicsSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
return jerry_boolean(entityPhysicsIsOnGround(c->entityId, c->componentId));
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(modulePhysicsApplyImpulse) {
|
||||||
|
moduleBaseRequireArgs(1);
|
||||||
|
jscomponent_t *c = modulePhysicsSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
float_t *v = moduleVec3From(args[0]);
|
||||||
|
if(!v) return moduleBaseThrow("Physics.applyImpulse: expected Vec3");
|
||||||
|
entityPhysicsApplyImpulse(c->entityId, c->componentId, v);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(modulePhysicsToString) {
|
||||||
|
jscomponent_t *c = modulePhysicsSelf(callInfo);
|
||||||
|
if(!c) return jerry_string_sz("Physics:invalid");
|
||||||
|
char_t buf[32];
|
||||||
|
snprintf(buf, sizeof(buf), "Physics(%u)", (unsigned)c->componentId);
|
||||||
|
return jerry_string_sz(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void modulePhysicsInit(void) {
|
||||||
|
scriptProtoInit(
|
||||||
|
&MODULE_PHYSICS_PROTO, "Physics",
|
||||||
|
sizeof(jscomponent_t), modulePhysicsCtor
|
||||||
|
);
|
||||||
|
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_PHYSICS_PROTO, "entity", modulePhysicsGetEntity, NULL
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_PHYSICS_PROTO, "id", modulePhysicsGetId, NULL
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_PHYSICS_PROTO, "bodyType",
|
||||||
|
modulePhysicsGetBodyType, modulePhysicsSetBodyType
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_PHYSICS_PROTO, "shape",
|
||||||
|
modulePhysicsGetShape, modulePhysicsSetShape
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_PHYSICS_PROTO, "velocity",
|
||||||
|
modulePhysicsGetVelocity, modulePhysicsSetVelocity
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_PHYSICS_PROTO, "gravityScale",
|
||||||
|
modulePhysicsGetGravityScale, modulePhysicsSetGravityScale
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_PHYSICS_PROTO, "onGround", modulePhysicsGetOnGround, NULL
|
||||||
|
);
|
||||||
|
scriptProtoDefineFunc(
|
||||||
|
&MODULE_PHYSICS_PROTO, "applyImpulse", modulePhysicsApplyImpulse
|
||||||
|
);
|
||||||
|
scriptProtoDefineToString(&MODULE_PHYSICS_PROTO, modulePhysicsToString);
|
||||||
|
|
||||||
|
/* Body type constants */
|
||||||
|
jerry_value_t ctor = MODULE_PHYSICS_PROTO.constructor;
|
||||||
|
struct { const char_t *name; int val; } bodyTypes[] = {
|
||||||
|
{ "STATIC", PHYSICS_BODY_STATIC },
|
||||||
|
{ "DYNAMIC", PHYSICS_BODY_DYNAMIC },
|
||||||
|
{ "KINEMATIC", PHYSICS_BODY_KINEMATIC },
|
||||||
|
};
|
||||||
|
for(int i = 0; i < 3; i++) {
|
||||||
|
jerry_value_t k = jerry_string_sz(bodyTypes[i].name);
|
||||||
|
jerry_value_t v = jerry_number((double)bodyTypes[i].val);
|
||||||
|
jerry_object_set(ctor, k, v);
|
||||||
|
jerry_value_free(v);
|
||||||
|
jerry_value_free(k);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Shape type constants */
|
||||||
|
struct { const char_t *name; int val; } shapes[] = {
|
||||||
|
{ "SHAPE_CUBE", PHYSICS_SHAPE_CUBE },
|
||||||
|
{ "SHAPE_SPHERE", PHYSICS_SHAPE_SPHERE },
|
||||||
|
{ "SHAPE_CAPSULE", PHYSICS_SHAPE_CAPSULE },
|
||||||
|
{ "SHAPE_PLANE", PHYSICS_SHAPE_PLANE },
|
||||||
|
};
|
||||||
|
for(int i = 0; i < 4; i++) {
|
||||||
|
jerry_value_t k = jerry_string_sz(shapes[i].name);
|
||||||
|
jerry_value_t v = jerry_number((double)shapes[i].val);
|
||||||
|
jerry_object_set(ctor, k, v);
|
||||||
|
jerry_value_free(v);
|
||||||
|
jerry_value_free(k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void modulePhysicsDispose(void) {
|
||||||
|
scriptProtoDispose(&MODULE_PHYSICS_PROTO);
|
||||||
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "script/module/modulebase.h"
|
||||||
|
#include "script/scriptproto.h"
|
||||||
|
#include "script/module/math/modulevec3.h"
|
||||||
|
#include "script/module/entity/modulecomponent.h"
|
||||||
|
#include "entity/component/trigger/entitytrigger.h"
|
||||||
|
|
||||||
|
static scriptproto_t MODULE_TRIGGER_PROTO;
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleTriggerCtor) {
|
||||||
|
(void)callInfo; (void)args; (void)argc;
|
||||||
|
return moduleBaseThrow("Trigger cannot be instantiated with new");
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline jscomponent_t *moduleTriggerSelf(
|
||||||
|
const jerry_call_info_t *callInfo
|
||||||
|
) {
|
||||||
|
return (jscomponent_t *)scriptProtoGetValue(
|
||||||
|
&MODULE_TRIGGER_PROTO, callInfo->this_value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleTriggerGetEntity) {
|
||||||
|
jscomponent_t *c = moduleTriggerSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
return jerry_number((double)c->entityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleTriggerGetId) {
|
||||||
|
jscomponent_t *c = moduleTriggerSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
return jerry_number((double)c->componentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleTriggerGetMin) {
|
||||||
|
jscomponent_t *c = moduleTriggerSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
entitytrigger_t *t = entityTriggerGet(c->entityId, c->componentId);
|
||||||
|
if(!t) return jerry_undefined();
|
||||||
|
return moduleVec3Push(t->min);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleTriggerSetMin) {
|
||||||
|
moduleBaseRequireArgs(1);
|
||||||
|
jscomponent_t *c = moduleTriggerSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
float_t *v = moduleVec3From(args[0]);
|
||||||
|
if(!v) return moduleBaseThrow("Trigger.min: expected Vec3");
|
||||||
|
entitytrigger_t *t = entityTriggerGet(c->entityId, c->componentId);
|
||||||
|
if(!t) return jerry_undefined();
|
||||||
|
glm_vec3_copy(v, t->min);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleTriggerGetMax) {
|
||||||
|
jscomponent_t *c = moduleTriggerSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
entitytrigger_t *t = entityTriggerGet(c->entityId, c->componentId);
|
||||||
|
if(!t) return jerry_undefined();
|
||||||
|
return moduleVec3Push(t->max);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleTriggerSetMax) {
|
||||||
|
moduleBaseRequireArgs(1);
|
||||||
|
jscomponent_t *c = moduleTriggerSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
float_t *v = moduleVec3From(args[0]);
|
||||||
|
if(!v) return moduleBaseThrow("Trigger.max: expected Vec3");
|
||||||
|
entitytrigger_t *t = entityTriggerGet(c->entityId, c->componentId);
|
||||||
|
if(!t) return jerry_undefined();
|
||||||
|
glm_vec3_copy(v, t->max);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleTriggerSetBounds) {
|
||||||
|
moduleBaseRequireArgs(2);
|
||||||
|
jscomponent_t *c = moduleTriggerSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
float_t *minV = moduleVec3From(args[0]);
|
||||||
|
float_t *maxV = moduleVec3From(args[1]);
|
||||||
|
if(!minV) return moduleBaseThrow("Trigger.setBounds: expected Vec3 for min");
|
||||||
|
if(!maxV) return moduleBaseThrow("Trigger.setBounds: expected Vec3 for max");
|
||||||
|
entityTriggerSetBounds(c->entityId, c->componentId, minV, maxV);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleTriggerContains) {
|
||||||
|
moduleBaseRequireArgs(1);
|
||||||
|
jscomponent_t *c = moduleTriggerSelf(callInfo);
|
||||||
|
if(!c) return jerry_undefined();
|
||||||
|
float_t *v = moduleVec3From(args[0]);
|
||||||
|
if(!v) return moduleBaseThrow("Trigger.contains: expected Vec3");
|
||||||
|
return jerry_boolean(entityTriggerContains(c->entityId, c->componentId, v));
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBaseFunction(moduleTriggerToString) {
|
||||||
|
jscomponent_t *c = moduleTriggerSelf(callInfo);
|
||||||
|
if(!c) return jerry_string_sz("Trigger:invalid");
|
||||||
|
char_t buf[32];
|
||||||
|
snprintf(buf, sizeof(buf), "Trigger(%u)", (unsigned)c->componentId);
|
||||||
|
return jerry_string_sz(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void moduleTriggerInit(void) {
|
||||||
|
scriptProtoInit(
|
||||||
|
&MODULE_TRIGGER_PROTO, "Trigger",
|
||||||
|
sizeof(jscomponent_t), moduleTriggerCtor
|
||||||
|
);
|
||||||
|
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_TRIGGER_PROTO, "entity", moduleTriggerGetEntity, NULL
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_TRIGGER_PROTO, "id", moduleTriggerGetId, NULL
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_TRIGGER_PROTO, "min", moduleTriggerGetMin, moduleTriggerSetMin
|
||||||
|
);
|
||||||
|
scriptProtoDefineProp(
|
||||||
|
&MODULE_TRIGGER_PROTO, "max", moduleTriggerGetMax, moduleTriggerSetMax
|
||||||
|
);
|
||||||
|
scriptProtoDefineFunc(
|
||||||
|
&MODULE_TRIGGER_PROTO, "setBounds", moduleTriggerSetBounds
|
||||||
|
);
|
||||||
|
scriptProtoDefineFunc(
|
||||||
|
&MODULE_TRIGGER_PROTO, "contains", moduleTriggerContains
|
||||||
|
);
|
||||||
|
scriptProtoDefineToString(&MODULE_TRIGGER_PROTO, moduleTriggerToString);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void moduleTriggerDispose(void) {
|
||||||
|
scriptProtoDispose(&MODULE_TRIGGER_PROTO);
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "script/module/modulebase.h"
|
#include "script/module/modulebase.h"
|
||||||
#include "script/scriptproto.h"
|
#include "script/scriptproto.h"
|
||||||
#include "script/module/entity/modulecomponent.h"
|
#include "script/module/entity/modulecomponent.h"
|
||||||
|
#include "script/module/entity/component/modulecomponentlist.h"
|
||||||
#include "entity/entitymanager.h"
|
#include "entity/entitymanager.h"
|
||||||
|
|
||||||
/** C struct wrapped by every Entity JS instance. */
|
/** C struct wrapped by every Entity JS instance. */
|
||||||
@@ -59,7 +60,7 @@ moduleBaseFunction(moduleEntityAdd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
jscomponent_t comp = { .entityId = ent->id, .componentId = cid };
|
jscomponent_t comp = { .entityId = ent->id, .componentId = cid };
|
||||||
return scriptProtoCreateValue(&MODULE_COMPONENT_PROTO, &comp);
|
return moduleComponentListCreateInstance(type, &comp);
|
||||||
}
|
}
|
||||||
|
|
||||||
moduleBaseFunction(moduleEntityToString) {
|
moduleBaseFunction(moduleEntityToString) {
|
||||||
|
|||||||
@@ -5,5 +5,5 @@
|
|||||||
|
|
||||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||||
PUBLIC
|
PUBLIC
|
||||||
testscene.c
|
moduleevent.c
|
||||||
)
|
)
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "moduleevent.h"
|
||||||
|
#include "util/memory.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
|
||||||
|
#define MODULE_EVENT_PENDING_MAX 32
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
event_t *event;
|
||||||
|
jerry_value_t promise;
|
||||||
|
} moduleeventpending_t;
|
||||||
|
|
||||||
|
scriptproto_t MODULE_EVENT_PROTO;
|
||||||
|
|
||||||
|
static moduleeventpending_t MODULE_EVENT_PENDING[MODULE_EVENT_PENDING_MAX];
|
||||||
|
static uint32_t MODULE_EVENT_PENDING_COUNT = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Single shared C callback subscribed to any event that has JS awaits.
|
||||||
|
* Resolves all pending promises for the fired event, then unsubscribes.
|
||||||
|
* The user pointer is the event_t * so we can look up and unsubscribe.
|
||||||
|
*/
|
||||||
|
static void moduleEventFireCallback(void *params, void *user) {
|
||||||
|
(void)params;
|
||||||
|
event_t *event = (event_t *)user;
|
||||||
|
|
||||||
|
uint32_t i = 0;
|
||||||
|
while(i < MODULE_EVENT_PENDING_COUNT) {
|
||||||
|
if(MODULE_EVENT_PENDING[i].event != event) { i++; continue; }
|
||||||
|
jerry_value_t ret = jerry_promise_resolve(
|
||||||
|
MODULE_EVENT_PENDING[i].promise, jerry_undefined()
|
||||||
|
);
|
||||||
|
jerry_value_free(ret);
|
||||||
|
jerry_value_free(MODULE_EVENT_PENDING[i].promise);
|
||||||
|
MODULE_EVENT_PENDING_COUNT--;
|
||||||
|
if(i < MODULE_EVENT_PENDING_COUNT) {
|
||||||
|
MODULE_EVENT_PENDING[i] = MODULE_EVENT_PENDING[MODULE_EVENT_PENDING_COUNT];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
eventUnsubscribe(event, moduleEventFireCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
static jerry_value_t moduleEventWait(
|
||||||
|
const jerry_call_info_t *callInfo,
|
||||||
|
const jerry_value_t args[],
|
||||||
|
const jerry_length_t argc
|
||||||
|
) {
|
||||||
|
(void)args; (void)argc;
|
||||||
|
|
||||||
|
jsevent_t *ev = scriptProtoGetValue(&MODULE_EVENT_PROTO, callInfo->this_value);
|
||||||
|
if(!ev) return moduleBaseThrow("Event.wait: invalid this");
|
||||||
|
|
||||||
|
if(MODULE_EVENT_PENDING_COUNT >= MODULE_EVENT_PENDING_MAX) {
|
||||||
|
return moduleBaseThrow("Event.wait: too many pending awaits");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only subscribe once per event — check if we already have a pending await
|
||||||
|
bool_t subscribed = false;
|
||||||
|
for(uint32_t i = 0; i < MODULE_EVENT_PENDING_COUNT; i++) {
|
||||||
|
if(MODULE_EVENT_PENDING[i].event == ev->event) { subscribed = true; break; }
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!subscribed) {
|
||||||
|
if(ev->event->count >= ev->event->size) {
|
||||||
|
return moduleBaseThrow("Event.wait: event subscriber capacity exceeded");
|
||||||
|
}
|
||||||
|
eventSubscribe(ev->event, moduleEventFireCallback, (void *)ev->event);
|
||||||
|
}
|
||||||
|
|
||||||
|
jerry_value_t promise = jerry_promise();
|
||||||
|
MODULE_EVENT_PENDING[MODULE_EVENT_PENDING_COUNT].event = ev->event;
|
||||||
|
MODULE_EVENT_PENDING[MODULE_EVENT_PENDING_COUNT].promise = jerry_value_copy(promise);
|
||||||
|
MODULE_EVENT_PENDING_COUNT++;
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
jerry_value_t moduleEventCreate(event_t *event) {
|
||||||
|
assertNotNull(event, "moduleEventCreate: event must not be NULL");
|
||||||
|
jsevent_t ev = { .event = event };
|
||||||
|
return scriptProtoCreateValue(&MODULE_EVENT_PROTO, &ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
void moduleEventInit(void) {
|
||||||
|
MODULE_EVENT_PENDING_COUNT = 0;
|
||||||
|
scriptProtoInit(&MODULE_EVENT_PROTO, NULL, sizeof(jsevent_t), NULL);
|
||||||
|
scriptProtoDefineFunc(&MODULE_EVENT_PROTO, "wait", moduleEventWait);
|
||||||
|
}
|
||||||
|
|
||||||
|
void moduleEventDispose(void) {
|
||||||
|
// Unsubscribe from each distinct event still in the pending list
|
||||||
|
for(uint32_t i = 0; i < MODULE_EVENT_PENDING_COUNT; i++) {
|
||||||
|
bool_t alreadyUnsub = false;
|
||||||
|
for(uint32_t j = 0; j < i; j++) {
|
||||||
|
if(MODULE_EVENT_PENDING[j].event == MODULE_EVENT_PENDING[i].event) {
|
||||||
|
alreadyUnsub = true; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!alreadyUnsub) {
|
||||||
|
eventUnsubscribe(MODULE_EVENT_PENDING[i].event, moduleEventFireCallback);
|
||||||
|
}
|
||||||
|
jerry_value_free(MODULE_EVENT_PENDING[i].promise);
|
||||||
|
}
|
||||||
|
MODULE_EVENT_PENDING_COUNT = 0;
|
||||||
|
scriptProtoDispose(&MODULE_EVENT_PROTO);
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "script/module/modulebase.h"
|
||||||
|
#include "script/scriptproto.h"
|
||||||
|
#include "event/event.h"
|
||||||
|
|
||||||
|
/** C struct wrapped by every Event JS instance. */
|
||||||
|
typedef struct {
|
||||||
|
event_t *event;
|
||||||
|
} jsevent_t;
|
||||||
|
|
||||||
|
extern scriptproto_t MODULE_EVENT_PROTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps a C event_t pointer in a JS Event object.
|
||||||
|
*
|
||||||
|
* @param event The event to wrap. Must outlive the returned JS value.
|
||||||
|
* @return A new JS Event instance.
|
||||||
|
*/
|
||||||
|
jerry_value_t moduleEventCreate(event_t *event);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the Event module and registers the global Event prototype.
|
||||||
|
*/
|
||||||
|
void moduleEventInit(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disposes the Event module, rejecting any pending awaits and cleaning up.
|
||||||
|
*/
|
||||||
|
void moduleEventDispose(void);
|
||||||
@@ -13,31 +13,17 @@
|
|||||||
|
|
||||||
static scriptproto_t MODULE_VEC3_PROTO;
|
static scriptproto_t MODULE_VEC3_PROTO;
|
||||||
|
|
||||||
/**
|
float_t * moduleVec3Get(const jerry_call_info_t *callInfo) {
|
||||||
* Returns the native float[3] pointer from the Vec3 instance that owns
|
|
||||||
* the current call (this_value). Returns NULL if not a Vec3.
|
|
||||||
*/
|
|
||||||
static inline float_t *moduleVec3Get(const jerry_call_info_t *callInfo) {
|
|
||||||
return (float_t *)scriptProtoGetValue(
|
return (float_t *)scriptProtoGetValue(
|
||||||
&MODULE_VEC3_PROTO, callInfo->this_value
|
&MODULE_VEC3_PROTO, callInfo->this_value
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
float_t * moduleVec3From(const jerry_value_t val) {
|
||||||
* Returns the native float[3] pointer from any jerry value.
|
|
||||||
* Returns NULL if the value is not a Vec3 instance.
|
|
||||||
*/
|
|
||||||
static inline float_t *moduleVec3From(const jerry_value_t val) {
|
|
||||||
return (float_t *)scriptProtoGetValue(&MODULE_VEC3_PROTO, val);
|
return (float_t *)scriptProtoGetValue(&MODULE_VEC3_PROTO, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
jerry_value_t moduleVec3Push(const vec3 v) {
|
||||||
* Creates a Vec3 JS object from a C vec3 array.
|
|
||||||
*
|
|
||||||
* @param v Source vec3 to copy.
|
|
||||||
* @return A new Vec3 JS instance owning a copy of the data.
|
|
||||||
*/
|
|
||||||
static inline jerry_value_t moduleVec3Push(const vec3 v) {
|
|
||||||
return scriptProtoCreateValue(&MODULE_VEC3_PROTO, v);
|
return scriptProtoCreateValue(&MODULE_VEC3_PROTO, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "modulelist.h"
|
||||||
|
#include "script/module/asset/moduleasset.h"
|
||||||
|
#include "script/module/console/moduleconsole.h"
|
||||||
|
#include "script/module/display/modulecolor.h"
|
||||||
|
#include "script/module/display/modulescreen.h"
|
||||||
|
#include "script/module/engine/moduleengine.h"
|
||||||
|
#include "script/module/engine/moduleframe.h"
|
||||||
|
#include "script/module/event/moduleevent.h"
|
||||||
|
#include "script/module/engine/moduletimeout.h"
|
||||||
|
#include "script/module/entity/component/modulecomponentlist.h"
|
||||||
|
#include "script/module/entity/modulecomponent.h"
|
||||||
|
#include "script/module/entity/moduleentity.h"
|
||||||
|
#include "script/module/input/moduleinput.h"
|
||||||
|
#include "script/module/math/modulevec3.h"
|
||||||
|
#include "script/module/require/modulerequire.h"
|
||||||
|
#include "script/module/scene/modulescene.h"
|
||||||
|
#include "script/module/system/modulesystem.h"
|
||||||
|
|
||||||
|
|
||||||
|
void moduleListInit(void) {
|
||||||
|
moduleEventInit();
|
||||||
|
moduleTextureInit();
|
||||||
|
moduleColorInit();
|
||||||
|
moduleAssetInit();
|
||||||
|
moduleConsoleInit();
|
||||||
|
moduleScreenInit();
|
||||||
|
moduleEngineInit();
|
||||||
|
moduleFrameInit();
|
||||||
|
moduleTimeoutInit();
|
||||||
|
moduleVec3Init();
|
||||||
|
moduleComponentInit();
|
||||||
|
moduleEntityInit();
|
||||||
|
moduleComponentListInit();
|
||||||
|
moduleInputInit();
|
||||||
|
moduleRequireInit();
|
||||||
|
moduleSceneInit();
|
||||||
|
moduleSystemInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void moduleListUpdate(void) {
|
||||||
|
moduleFrameFlush();
|
||||||
|
moduleTimeoutFlush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void moduleListDispose(void) {
|
||||||
|
moduleSystemDispose();
|
||||||
|
moduleSceneDispose();
|
||||||
|
moduleRequireDispose();
|
||||||
|
moduleInputDispose();
|
||||||
|
moduleComponentListDispose();
|
||||||
|
moduleEntityDispose();
|
||||||
|
moduleComponentDispose();
|
||||||
|
moduleVec3Dispose();
|
||||||
|
moduleTimeoutDispose();
|
||||||
|
moduleFrameDispose();
|
||||||
|
moduleEngineDispose();
|
||||||
|
moduleScreenDispose();
|
||||||
|
moduleConsoleDispose();
|
||||||
|
moduleAssetDispose();
|
||||||
|
moduleColorDispose();
|
||||||
|
moduleTextureDispose();
|
||||||
|
moduleEventDispose();
|
||||||
|
}
|
||||||
@@ -6,39 +6,20 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "script/module/console/moduleconsole.h"
|
#include "dusk.h"
|
||||||
#include "script/module/display/modulescreen.h"
|
|
||||||
#include "script/module/engine/moduleengine.h"
|
|
||||||
#include "script/module/entity/component/modulecomponentlist.h"
|
|
||||||
#include "script/module/entity/modulecomponent.h"
|
|
||||||
#include "script/module/entity/moduleentity.h"
|
|
||||||
#include "script/module/input/moduleinput.h"
|
|
||||||
#include "script/module/math/modulevec3.h"
|
|
||||||
#include "script/module/scene/modulescene.h"
|
|
||||||
#include "script/module/system/modulesystem.h"
|
|
||||||
|
|
||||||
static void moduleListInit(void) {
|
/**
|
||||||
moduleConsoleInit();
|
* Initializes all of the internal (C) script modules.
|
||||||
moduleScreenInit();
|
*/
|
||||||
moduleEngineInit();
|
void moduleListInit(void);
|
||||||
moduleVec3Init();
|
|
||||||
moduleComponentInit();
|
|
||||||
moduleEntityInit();
|
|
||||||
moduleComponentListInit();
|
|
||||||
moduleInputInit();
|
|
||||||
moduleSceneInit();
|
|
||||||
moduleSystemInit();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void moduleListDispose(void) {
|
/**
|
||||||
moduleSystemDispose();
|
* Flushes per-frame module state. Must be called once per frame before
|
||||||
moduleSceneDispose();
|
* jerry_run_jobs() so that frame() promises resolve in the same tick.
|
||||||
moduleInputDispose();
|
*/
|
||||||
moduleComponentListDispose();
|
void moduleListUpdate(void);
|
||||||
moduleEntityDispose();
|
|
||||||
moduleComponentDispose();
|
/**
|
||||||
moduleVec3Dispose();
|
* Disposes all of the internal (C) script modules.
|
||||||
moduleEngineDispose();
|
*/
|
||||||
moduleScreenDispose();
|
void moduleListDispose(void);
|
||||||
moduleConsoleDispose();
|
|
||||||
}
|
|
||||||
+1
-2
@@ -3,8 +3,7 @@
|
|||||||
# This software is released under the MIT License.
|
# This software is released under the MIT License.
|
||||||
# https://opensource.org/licenses/MIT
|
# https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
# Sources
|
|
||||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||||
PUBLIC
|
PUBLIC
|
||||||
initialscene.c
|
modulerequire.c
|
||||||
)
|
)
|
||||||
@@ -0,0 +1,238 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "modulerequire.h"
|
||||||
|
#include "util/memory.h"
|
||||||
|
#include "util/string.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
|
||||||
|
modulerequireasyncpending_t MODULE_REQUIRE_ASYNC_PENDING[
|
||||||
|
MODULE_REQUIRE_ASYNC_MAX
|
||||||
|
];
|
||||||
|
uint32_t MODULE_REQUIRE_ASYNC_PENDING_COUNT = 0;
|
||||||
|
|
||||||
|
void moduleRequireAsyncOnLoaded(void *params, void *user) {
|
||||||
|
assetentry_t *entry = (assetentry_t *)params;
|
||||||
|
|
||||||
|
eventUnsubscribe(&entry->onLoaded, moduleRequireAsyncOnLoaded);
|
||||||
|
eventUnsubscribe(&entry->onError, moduleRequireAsyncOnError);
|
||||||
|
|
||||||
|
jerry_value_t exports = (
|
||||||
|
jerry_value_is_undefined(entry->data.script.exports) ?
|
||||||
|
jerry_undefined() :
|
||||||
|
jerry_value_copy(entry->data.script.exports)
|
||||||
|
);
|
||||||
|
|
||||||
|
uint32_t i = 0;
|
||||||
|
while(i < MODULE_REQUIRE_ASYNC_PENDING_COUNT) {
|
||||||
|
if(MODULE_REQUIRE_ASYNC_PENDING[i].entry != entry) {
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
assetUnlockEntry(entry);
|
||||||
|
|
||||||
|
jerry_value_t copy = jerry_value_copy(exports);
|
||||||
|
jerry_value_t ret = jerry_promise_resolve(
|
||||||
|
MODULE_REQUIRE_ASYNC_PENDING[i].promise, copy
|
||||||
|
);
|
||||||
|
jerry_value_free(ret);
|
||||||
|
jerry_value_free(copy);
|
||||||
|
jerry_value_free(MODULE_REQUIRE_ASYNC_PENDING[i].promise);
|
||||||
|
|
||||||
|
MODULE_REQUIRE_ASYNC_PENDING_COUNT--;
|
||||||
|
|
||||||
|
if(i < MODULE_REQUIRE_ASYNC_PENDING_COUNT) {
|
||||||
|
MODULE_REQUIRE_ASYNC_PENDING[i] = (
|
||||||
|
MODULE_REQUIRE_ASYNC_PENDING[MODULE_REQUIRE_ASYNC_PENDING_COUNT]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jerry_value_free(exports);
|
||||||
|
}
|
||||||
|
|
||||||
|
void moduleRequireAsyncOnError(void *params, void *user) {
|
||||||
|
assetentry_t *entry = (assetentry_t *)params;
|
||||||
|
|
||||||
|
eventUnsubscribe(&entry->onLoaded, moduleRequireAsyncOnLoaded);
|
||||||
|
eventUnsubscribe(&entry->onError, moduleRequireAsyncOnError);
|
||||||
|
|
||||||
|
jerry_value_t errStr = jerry_string_sz("Module load failed");
|
||||||
|
|
||||||
|
uint32_t i = 0;
|
||||||
|
while(i < MODULE_REQUIRE_ASYNC_PENDING_COUNT) {
|
||||||
|
if(MODULE_REQUIRE_ASYNC_PENDING[i].entry != entry) { i++; continue; }
|
||||||
|
assetUnlockEntry(entry);
|
||||||
|
|
||||||
|
jerry_value_t copy = jerry_value_copy(errStr);
|
||||||
|
jerry_value_t ret = jerry_promise_reject(
|
||||||
|
MODULE_REQUIRE_ASYNC_PENDING[i].promise, copy
|
||||||
|
);
|
||||||
|
jerry_value_free(ret);
|
||||||
|
jerry_value_free(copy);
|
||||||
|
jerry_value_free(MODULE_REQUIRE_ASYNC_PENDING[i].promise);
|
||||||
|
MODULE_REQUIRE_ASYNC_PENDING_COUNT--;
|
||||||
|
|
||||||
|
if(i < MODULE_REQUIRE_ASYNC_PENDING_COUNT) {
|
||||||
|
MODULE_REQUIRE_ASYNC_PENDING[i] = (
|
||||||
|
MODULE_REQUIRE_ASYNC_PENDING[MODULE_REQUIRE_ASYNC_PENDING_COUNT]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jerry_value_free(errStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
jerry_value_t moduleRequireFunc(
|
||||||
|
const jerry_call_info_t *callInfo,
|
||||||
|
const jerry_value_t args[],
|
||||||
|
const jerry_length_t argc
|
||||||
|
) {
|
||||||
|
assertNotNull(callInfo, "callInfo must not be null.");
|
||||||
|
assertNotNull(args, "args must not be null.");
|
||||||
|
|
||||||
|
if(argc < 1 || !jerry_value_is_string(args[0])) {
|
||||||
|
return moduleBaseThrow("Expected a string argument for module name.");
|
||||||
|
}
|
||||||
|
if(jerry_string_size(args[0], JERRY_ENCODING_UTF8) >= ASSET_FILE_NAME_MAX) {
|
||||||
|
return moduleBaseThrow("Module name too long.");
|
||||||
|
}
|
||||||
|
|
||||||
|
char_t moduleName[ASSET_FILE_NAME_MAX];
|
||||||
|
moduleBaseToString(args[0], moduleName, sizeof(moduleName));
|
||||||
|
|
||||||
|
assetloaderinput_t input;
|
||||||
|
input.script.nothing = NULL;
|
||||||
|
assetentry_t *entry = assetLock(moduleName, ASSET_LOADER_TYPE_SCRIPT, &input);
|
||||||
|
|
||||||
|
errorret_t err = assetRequireLoaded(entry);
|
||||||
|
if(errorIsNotOk(err)) {
|
||||||
|
assetUnlockEntry(entry);
|
||||||
|
return moduleBaseThrowError(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(jerry_value_is_undefined(entry->data.script.exports)) {
|
||||||
|
assetUnlockEntry(entry);
|
||||||
|
return jerry_undefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
jerry_value_t exportsCopy = jerry_value_copy(entry->data.script.exports);
|
||||||
|
assetUnlockEntry(entry);
|
||||||
|
return exportsCopy;
|
||||||
|
}
|
||||||
|
|
||||||
|
jerry_value_t moduleRequireAsyncFunc(
|
||||||
|
const jerry_call_info_t *callInfo,
|
||||||
|
const jerry_value_t args[],
|
||||||
|
const jerry_length_t argc
|
||||||
|
) {
|
||||||
|
assertNotNull(callInfo, "callInfo must not be null.");
|
||||||
|
assertNotNull(args, "args must not be null.");
|
||||||
|
|
||||||
|
if(argc < 1 || !jerry_value_is_string(args[0])) {
|
||||||
|
return moduleBaseThrow("requireAsync expects a filename string.");
|
||||||
|
}
|
||||||
|
if(jerry_string_size(args[0], JERRY_ENCODING_UTF8) >= ASSET_FILE_NAME_MAX) {
|
||||||
|
return moduleBaseThrow("Module name too long.");
|
||||||
|
}
|
||||||
|
if(MODULE_REQUIRE_ASYNC_PENDING_COUNT >= MODULE_REQUIRE_ASYNC_MAX) {
|
||||||
|
return moduleBaseThrow("Too many pending requireAsync calls.");
|
||||||
|
}
|
||||||
|
|
||||||
|
char_t moduleName[ASSET_FILE_NAME_MAX];
|
||||||
|
moduleBaseToString(args[0], moduleName, sizeof(moduleName));
|
||||||
|
|
||||||
|
assetloaderinput_t input;
|
||||||
|
input.script.nothing = NULL;
|
||||||
|
assetentry_t *entry = assetLock(moduleName, ASSET_LOADER_TYPE_SCRIPT, &input);
|
||||||
|
|
||||||
|
jerry_value_t promise = jerry_promise();
|
||||||
|
|
||||||
|
// Already loaded — resolve immediately.
|
||||||
|
if(entry->state == ASSET_ENTRY_STATE_LOADED) {
|
||||||
|
jerry_value_t exports = (
|
||||||
|
jerry_value_is_undefined(entry->data.script.exports) ?
|
||||||
|
jerry_undefined() :
|
||||||
|
jerry_value_copy(entry->data.script.exports)
|
||||||
|
);
|
||||||
|
assetUnlockEntry(entry);
|
||||||
|
jerry_value_t ret = jerry_promise_resolve(promise, exports);
|
||||||
|
jerry_value_free(ret);
|
||||||
|
jerry_value_free(exports);
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Already errored — reject immediately.
|
||||||
|
if(entry->state == ASSET_ENTRY_STATE_ERROR) {
|
||||||
|
assetUnlockEntry(entry);
|
||||||
|
jerry_value_t errStr = jerry_string_sz("Module load failed");
|
||||||
|
jerry_value_t ret = jerry_promise_reject(promise, errStr);
|
||||||
|
jerry_value_free(ret);
|
||||||
|
jerry_value_free(errStr);
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe to entry events only if this is the first pending await for it.
|
||||||
|
bool_t subscribed = false;
|
||||||
|
for(uint32_t i = 0; i < MODULE_REQUIRE_ASYNC_PENDING_COUNT; i++) {
|
||||||
|
if(MODULE_REQUIRE_ASYNC_PENDING[i].entry == entry) {
|
||||||
|
subscribed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!subscribed) {
|
||||||
|
if(entry->onLoaded.count >= entry->onLoaded.size) {
|
||||||
|
assetUnlockEntry(entry);
|
||||||
|
jerry_value_free(promise);
|
||||||
|
return moduleBaseThrow("requireAsync: onLoaded event capacity exceeded.");
|
||||||
|
}
|
||||||
|
if(entry->onError.count >= entry->onError.size) {
|
||||||
|
assetUnlockEntry(entry);
|
||||||
|
jerry_value_free(promise);
|
||||||
|
return moduleBaseThrow("requireAsync: onError event capacity exceeded.");
|
||||||
|
}
|
||||||
|
eventSubscribe(&entry->onLoaded, moduleRequireAsyncOnLoaded, NULL);
|
||||||
|
eventSubscribe(&entry->onError, moduleRequireAsyncOnError, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
MODULE_REQUIRE_ASYNC_PENDING[MODULE_REQUIRE_ASYNC_PENDING_COUNT].entry = (
|
||||||
|
entry
|
||||||
|
);
|
||||||
|
MODULE_REQUIRE_ASYNC_PENDING[MODULE_REQUIRE_ASYNC_PENDING_COUNT].promise = (
|
||||||
|
jerry_value_copy(promise)
|
||||||
|
);
|
||||||
|
MODULE_REQUIRE_ASYNC_PENDING_COUNT++;
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
void moduleRequireInit(void) {
|
||||||
|
MODULE_REQUIRE_ASYNC_PENDING_COUNT = 0;
|
||||||
|
moduleBaseDefineGlobalMethod("require", moduleRequireFunc);
|
||||||
|
moduleBaseDefineGlobalMethod("requireAsync", moduleRequireAsyncFunc);
|
||||||
|
}
|
||||||
|
|
||||||
|
void moduleRequireDispose(void) {
|
||||||
|
for(uint32_t i = 0; i < MODULE_REQUIRE_ASYNC_PENDING_COUNT; i++) {
|
||||||
|
assetentry_t *entry = MODULE_REQUIRE_ASYNC_PENDING[i].entry;
|
||||||
|
bool_t alreadyUnsub = false;
|
||||||
|
for(uint32_t j = 0; j < i; j++) {
|
||||||
|
if(MODULE_REQUIRE_ASYNC_PENDING[j].entry == entry) {
|
||||||
|
alreadyUnsub = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!alreadyUnsub) {
|
||||||
|
eventUnsubscribe(&entry->onLoaded, moduleRequireAsyncOnLoaded);
|
||||||
|
eventUnsubscribe(&entry->onError, moduleRequireAsyncOnError);
|
||||||
|
}
|
||||||
|
assetUnlockEntry(entry);
|
||||||
|
jerry_value_free(MODULE_REQUIRE_ASYNC_PENDING[i].promise);
|
||||||
|
}
|
||||||
|
MODULE_REQUIRE_ASYNC_PENDING_COUNT = 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "script/module/modulebase.h"
|
||||||
|
#include "asset/asset.h"
|
||||||
|
|
||||||
|
#define MODULE_REQUIRE_ASYNC_MAX 16
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
assetentry_t *entry;
|
||||||
|
jerry_value_t promise;
|
||||||
|
} modulerequireasyncpending_t;
|
||||||
|
|
||||||
|
extern modulerequireasyncpending_t MODULE_REQUIRE_ASYNC_PENDING[
|
||||||
|
MODULE_REQUIRE_ASYNC_MAX
|
||||||
|
];
|
||||||
|
|
||||||
|
extern uint32_t MODULE_REQUIRE_ASYNC_PENDING_COUNT;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success loaded callback for async module loading.
|
||||||
|
*
|
||||||
|
* @param params The asset entry that finished loading.
|
||||||
|
* @param user Unused.
|
||||||
|
*/
|
||||||
|
void moduleRequireAsyncOnLoaded(void *params, void *user);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error callback for when an async loaded module fails.
|
||||||
|
*
|
||||||
|
* @param params The asset entry that failed to load.
|
||||||
|
* @param user Unused.
|
||||||
|
*/
|
||||||
|
void moduleRequireAsyncOnError(void *params, void *user);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronous require function exposed to scripts.
|
||||||
|
*
|
||||||
|
* @param callInfo JerryScript call info.
|
||||||
|
* @param args The arguments passed to the function.
|
||||||
|
* @param argc The number of arguments passed.
|
||||||
|
* @return The exports of the required module.
|
||||||
|
*/
|
||||||
|
jerry_value_t moduleRequireFunc(
|
||||||
|
const jerry_call_info_t *callInfo,
|
||||||
|
const jerry_value_t args[],
|
||||||
|
const jerry_length_t argc
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asynchronous require function exposed to scripts.
|
||||||
|
*
|
||||||
|
* @param callInfo JerryScript call info.
|
||||||
|
* @param args The arguments passed to the function.
|
||||||
|
* @param argc The number of arguments passed.
|
||||||
|
* @return A promise that resolves with the exports of the required module.
|
||||||
|
*/
|
||||||
|
jerry_value_t moduleRequireAsyncFunc(
|
||||||
|
const jerry_call_info_t *callInfo,
|
||||||
|
const jerry_value_t args[],
|
||||||
|
const jerry_length_t argc
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the require module.
|
||||||
|
*/
|
||||||
|
void moduleRequireInit(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disposes the require module.
|
||||||
|
*/
|
||||||
|
void moduleRequireDispose(void);
|
||||||
@@ -7,8 +7,12 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "script/module/modulebase.h"
|
#include "script/module/modulebase.h"
|
||||||
|
#include "script/module/asset/moduleassetbatch.h"
|
||||||
#include "script/scriptproto.h"
|
#include "script/scriptproto.h"
|
||||||
#include "scene/scene.h"
|
#include "scene/scene.h"
|
||||||
|
#include "asset/asset.h"
|
||||||
|
#include "asset/loader/assetloader.h"
|
||||||
|
#include "util/memory.h"
|
||||||
|
|
||||||
static scriptproto_t MODULE_SCENE_PROTO;
|
static scriptproto_t MODULE_SCENE_PROTO;
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,28 @@ errorret_t scriptInit(void) {
|
|||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
errorret_t scriptUpdate() {
|
||||||
|
moduleListUpdate();
|
||||||
|
jerry_value_t ret = jerry_run_jobs();
|
||||||
|
|
||||||
|
if(jerry_value_is_exception(ret)) {
|
||||||
|
char_t buf[256];
|
||||||
|
jerry_value_t errVal = jerry_exception_value(ret, false);
|
||||||
|
jerry_value_t errStr = jerry_value_to_string(errVal);
|
||||||
|
jerry_size_t len = jerry_string_to_buffer(
|
||||||
|
errStr, JERRY_ENCODING_UTF8, (jerry_char_t *)buf, sizeof(buf) - 1
|
||||||
|
);
|
||||||
|
buf[len] = '\0';
|
||||||
|
jerry_value_free(errStr);
|
||||||
|
jerry_value_free(errVal);
|
||||||
|
jerry_value_free(ret);
|
||||||
|
errorThrow("Script error: %s", buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
jerry_value_free(ret);
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
errorret_t scriptExecString(const char_t *source) {
|
errorret_t scriptExecString(const char_t *source) {
|
||||||
assertNotNull(source, "Source cannot be NULL");
|
assertNotNull(source, "Source cannot be NULL");
|
||||||
assertTrue(SCRIPT.initialized, "Script system not initialized");
|
assertTrue(SCRIPT.initialized, "Script system not initialized");
|
||||||
@@ -69,8 +91,26 @@ errorret_t scriptExecFile(const char_t *path) {
|
|||||||
|
|
||||||
errorret_t scriptDispose(void) {
|
errorret_t scriptDispose(void) {
|
||||||
if(!SCRIPT.initialized) errorOk();
|
if(!SCRIPT.initialized) errorOk();
|
||||||
|
|
||||||
|
// Make a long story short we need to dispose script assets here, because the
|
||||||
|
// asset reaper isn't called until later.
|
||||||
|
assetentry_t *entries[ASSET_ENTRY_COUNT_MAX];
|
||||||
|
uint32_t count = assetGetEntriesOfType(entries, ASSET_LOADER_TYPE_SCRIPT);
|
||||||
|
|
||||||
|
// Release the locks
|
||||||
|
for(size_t i = 0; i < count; i++) {
|
||||||
|
assetUnlockEntry(entries[i]);
|
||||||
|
assetRequireDisposed(entries[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue(
|
||||||
|
assetGetEntriesOfType(entries, ASSET_LOADER_TYPE_SCRIPT) == 0,
|
||||||
|
"All script assets should be disposed by now."
|
||||||
|
);
|
||||||
|
|
||||||
moduleListDispose();
|
moduleListDispose();
|
||||||
jerry_cleanup();
|
jerry_cleanup();
|
||||||
SCRIPT.initialized = false;
|
SCRIPT.initialized = false;
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,14 @@ extern script_t SCRIPT;
|
|||||||
*/
|
*/
|
||||||
errorret_t scriptInit(void);
|
errorret_t scriptInit(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the script system, running any pending jobs. This should be called
|
||||||
|
* once per frame.
|
||||||
|
*
|
||||||
|
* @return Any error that occurred.
|
||||||
|
*/
|
||||||
|
errorret_t scriptUpdate(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Evaluates a JS source string in the global scope.
|
* Evaluates a JS source string in the global scope.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -6,12 +6,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "error/error.h"
|
#include "scriptproto.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
void *nothing;
|
jerry_value_t exports;
|
||||||
} testscene_t;
|
} scriptmodule_t;
|
||||||
|
|
||||||
void testSceneInit(void);
|
|
||||||
errorret_t testSceneUpdate(void);
|
|
||||||
void testSceneDispose(void);
|
|
||||||
@@ -8,6 +8,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "dusk.h"
|
#include "dusk.h"
|
||||||
|
|
||||||
|
#define MATH_PI M_PI
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds the next power of two greater than or equal to the given value.
|
* Finds the next power of two greater than or equal to the given value.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -7,20 +7,74 @@
|
|||||||
|
|
||||||
#include "log/log.h"
|
#include "log/log.h"
|
||||||
#include "display/display.h"
|
#include "display/display.h"
|
||||||
#include <debug.h>
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#if DUSK_DOLPHIN_BUILD_TYPE == DOL
|
||||||
|
#include <debug.h>
|
||||||
|
#include <fat.h>
|
||||||
|
|
||||||
|
#define LOG_DEBUG_PATH "/apps/Dusk/debug.log"
|
||||||
|
#define LOG_ERROR_PATH "/apps/Dusk/error.log"
|
||||||
|
|
||||||
|
static bool_t fatTried = false;
|
||||||
|
static bool_t fatReady = false;
|
||||||
|
|
||||||
|
static void logInitFAT(void) {
|
||||||
|
if(fatTried) return;
|
||||||
|
fatTried = true;
|
||||||
|
fatReady = fatInitDefault();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void logDebug(const char_t *message, ...) {
|
void logDebug(const char_t *message, ...) {
|
||||||
// Print to stdout
|
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, message);
|
va_start(args, message);
|
||||||
vfprintf(stdout, message, args);
|
|
||||||
va_end(args);
|
// Print to stdout
|
||||||
|
va_list copy;
|
||||||
|
va_copy(copy, args);
|
||||||
|
vfprintf(stdout, message, copy);
|
||||||
|
va_end(copy);
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
|
|
||||||
|
#if DUSK_DOLPHIN_BUILD_TYPE == DOL
|
||||||
|
// Print to file
|
||||||
|
logInitFAT();
|
||||||
|
if(fatReady) {
|
||||||
|
FILE *file = fopen(LOG_DEBUG_PATH, "a");
|
||||||
|
if(file) {
|
||||||
|
va_copy(copy, args);
|
||||||
|
vfprintf(file, message, copy);
|
||||||
|
va_end(copy);
|
||||||
|
fclose(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
va_end(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
void logError(const char_t *message, ...) {
|
void logError(const char_t *message, ...) {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, message);
|
||||||
|
|
||||||
|
// Write to file before displaying on screen
|
||||||
|
#if DUSK_DOLPHIN_BUILD_TYPE == DOL
|
||||||
|
logInitFAT();
|
||||||
|
if(fatReady) {
|
||||||
|
FILE *file = fopen(LOG_ERROR_PATH, "a");
|
||||||
|
if(file) {
|
||||||
|
va_list copy;
|
||||||
|
va_copy(copy, args);
|
||||||
|
vfprintf(file, message, copy);
|
||||||
|
va_end(copy);
|
||||||
|
fclose(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Either create graphics, or hijack the displays' graphics.
|
// Either create graphics, or hijack the displays' graphics.
|
||||||
void *xfb = NULL;
|
|
||||||
GXRModeObj *rmode = NULL;
|
GXRModeObj *rmode = NULL;
|
||||||
void *framebuffer;
|
void *framebuffer;
|
||||||
|
|
||||||
@@ -54,12 +108,24 @@ void logError(const char_t *message, ...) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Printf
|
// Printf
|
||||||
va_list args;
|
va_list copy;
|
||||||
va_start(args, message);
|
va_copy(copy, args);
|
||||||
vprintf(message, args);
|
vprintf(message, copy);
|
||||||
|
va_end(copy);
|
||||||
va_end(args);
|
va_end(args);
|
||||||
|
|
||||||
|
// PAD_Init is idempotent — safe to call even if inputInit already called it,
|
||||||
|
// and handles the case where the error occurred before inputInit ran.
|
||||||
|
PAD_Init();
|
||||||
while(SYS_MainLoop()) {
|
while(SYS_MainLoop()) {
|
||||||
|
PAD_ScanPads();
|
||||||
|
// START button matches the RAGEQUIT bind — allows exit on GC controller
|
||||||
|
// when there is no Wiimote HOME button available.
|
||||||
|
if(PAD_ButtonsDown(0) & PAD_BUTTON_START) break;
|
||||||
VIDEO_WaitVSync();
|
VIDEO_WaitVSync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Exit cleanly to HBC regardless of engine state. engineDispose() is not
|
||||||
|
// called here because the engine may be partially initialized.
|
||||||
|
exit(0);
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user