summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJonathan Bradley <jcb@pikum.xyz>2025-03-21 20:54:14 -0400
committerJonathan Bradley <jcb@pikum.xyz>2025-03-21 21:40:21 -0400
commit677a3cbec2f7908ee0897b97d508a6bd66a0a344 (patch)
treefc88b21dd61dbb10dd8b5c8aef73702d15514f00 /src
parentcae76dd98e301a4560bb46ecb59b5952dff04149 (diff)
pke: first-pass level is a collection of scenes
Diffstat (limited to 'src')
-rw-r--r--src/arg-handler.cpp6
-rw-r--r--src/bucketed-array.hpp10
-rw-r--r--src/components.hpp4
-rw-r--r--src/ecs.cpp4
-rw-r--r--src/entities.cpp3
-rw-r--r--src/game-settings.hpp4
-rw-r--r--src/game.cpp57
-rw-r--r--src/game.hpp3
-rw-r--r--src/level-types.hpp19
-rw-r--r--src/level.cpp109
-rw-r--r--src/level.hpp11
-rw-r--r--src/scene-types.hpp18
-rw-r--r--src/scene.cpp96
-rw-r--r--src/scene.hpp20
14 files changed, 279 insertions, 85 deletions
diff --git a/src/arg-handler.cpp b/src/arg-handler.cpp
index 24695fe..f192584 100644
--- a/src/arg-handler.cpp
+++ b/src/arg-handler.cpp
@@ -9,6 +9,7 @@ void PkeArgs_Parse(int argc, char *argv[]) {
while (1) {
static struct option long_options[] = {
+ {"level", required_argument, 0, 'l'},
{"plugin", required_argument, 0, 'p'},
{"project", required_argument, 0, 'r'},
{"scene", required_argument, 0, 's'},
@@ -24,6 +25,9 @@ void PkeArgs_Parse(int argc, char *argv[]) {
switch (c) {
case 0:
break;
+ case 'l':
+ pkeSettings.args.levelName = optarg;
+ break;
case 'p':
pkeSettings.args.pluginPath = optarg;
break;
@@ -32,8 +36,6 @@ void PkeArgs_Parse(int argc, char *argv[]) {
break;
case 's':
pkeSettings.args.sceneName = optarg;
- pkeSettings.rt.shouldLoadScene = true;
- pkeSettings.rt.sceneName = pkeSettings.args.sceneName;
break;
default:
fprintf(stderr, "Unused parameter: %c\n", c);
diff --git a/src/bucketed-array.hpp b/src/bucketed-array.hpp
index fb24712..150fa66 100644
--- a/src/bucketed-array.hpp
+++ b/src/bucketed-array.hpp
@@ -14,9 +14,19 @@ struct BucketContainer {
CT limits;
CT pkeHandle;
T* buckets[BKT_CNT];
+ T& operator[](CT) const;
};
template<typename T, typename CT, pk_handle_item_index_T BKT_CNT>
+T&
+BucketContainer<T, CT, BKT_CNT>::operator[](CT handle) const {
+ assert(handle.bucketIndex < this->limits.bucketIndex);
+ assert(handle.itemIndex < this->limits.itemIndex);
+ assert(handle.bucketIndex != this->pkeHandle.bucketIndex || handle.itemIndex < this->pkeHandle.itemIndex);
+ return this->buckets[handle.bucketIndex][handle.itemIndex];
+}
+
+template<typename T, typename CT, pk_handle_item_index_T BKT_CNT>
void Buckets_Init(BucketContainer<T, CT, BKT_CNT> &bktContainer, size_t maxItemCount = BucketContainerDefaultItemCount) {
bktContainer.limits.bucketIndex = BKT_CNT;
bktContainer.limits.itemIndex = maxItemCount;
diff --git a/src/components.hpp b/src/components.hpp
index b587fcc..326b0e6 100644
--- a/src/components.hpp
+++ b/src/components.hpp
@@ -16,10 +16,14 @@ const uint32_t ECS_UNSET_VAL_32 = 0xFFFFFFFF;
struct EntityHandle : public pk_handle { };
struct GrBindsHandle : public pk_handle { };
struct InstanceHandle : public pk_handle { };
+struct SceneHandle : public pk_handle { };
+struct LevelHandle : public pk_handle { };
constexpr EntityHandle EntityHandle_MAX = EntityHandle{ pk_handle_MAX_constexpr };
constexpr GrBindsHandle GrBindsHandle_MAX = GrBindsHandle{ pk_handle_MAX_constexpr };
constexpr InstanceHandle InstanceHandle_MAX = InstanceHandle{ pk_handle_MAX_constexpr };
+constexpr SceneHandle SceneHandle_MAX = SceneHandle{ pk_handle_MAX_constexpr };
+constexpr LevelHandle LevelHandle_MAX = LevelHandle{ pk_handle_MAX_constexpr };
struct Entity_Base {
EntityHandle handle = EntityHandle_MAX;
diff --git a/src/ecs.cpp b/src/ecs.cpp
index 3ab8a80..4174bd0 100644
--- a/src/ecs.cpp
+++ b/src/ecs.cpp
@@ -83,8 +83,8 @@ EntityHandle ECS_CreateEntity(Entity_Base *entity, Entity_Base *parentEntity) {
}
Entity_Base *ECS_GetEntity(EntityHandle handle) {
- pk_handle_validate(handle, ecs.bc.entityPtrs.pkeHandle, ecs.bc.entityPtrs.limits.itemIndex);
- return ecs.bc.entityPtrs.buckets[handle.bucketIndex][handle.itemIndex];
+ if (pk_handle_validate(handle, ecs.bc.entityPtrs.pkeHandle, ecs.bc.entityPtrs.limits.itemIndex) != PK_HANDLE_VALIDATION_VALID) return nullptr;
+ return ecs.bc.entityPtrs[handle];
}
void ECS_MarkForRemoval(Entity_Base *entity) {
diff --git a/src/entities.cpp b/src/entities.cpp
index 1abe632..2ec6c60 100644
--- a/src/entities.cpp
+++ b/src/entities.cpp
@@ -108,6 +108,9 @@ EntityType *EntityType_FindByTypeCode(const char *typeCode) {
EntityType *EntityType_FindByEntityHandle_Inner(EntityHandle handle) {
if (handle == EntityHandle_MAX) return nullptr;
+ if (handle.bucketIndex > EntityType_BC.limits.bucketIndex) return nullptr;
+ if (handle.itemIndex > EntityType_BC.limits.itemIndex) return nullptr;
+ if (handle.bucketIndex == EntityType_BC.pkeHandle.bucketIndex && handle.itemIndex >= EntityType_BC.pkeHandle.bucketIndex) return nullptr;
for (pk_handle_bucket_index_T b = 0; b <= EntityType_BC.pkeHandle.bucketIndex; ++b) {
auto &bkt = EntityType_BC.buckets[b];
long itemCount = EntityType_BC.pkeHandle.bucketIndex == b ? EntityType_BC.pkeHandle.itemIndex : EntityType_BC.limits.itemIndex;
diff --git a/src/game-settings.hpp b/src/game-settings.hpp
index 65f4242..ba078b9 100644
--- a/src/game-settings.hpp
+++ b/src/game-settings.hpp
@@ -34,6 +34,7 @@ struct GameSettings {
struct pk_membucket *bkt = nullptr;
} mem;
struct engineArgs {
+ const char *levelName = nullptr;
const char *pluginPath = nullptr;
const char *projectPath = nullptr;
const char *sceneName = nullptr;
@@ -45,9 +46,6 @@ struct GameSettings {
LevelHandle nextLevel = LevelHandle_MAX;
// level to unload
LevelHandle previousLevel = LevelHandle_MAX;
- const char *sceneName = nullptr;
- bool shouldLoadScene = false;
- bool shouldSaveScene = false;
bool was_framebuffer_resized = false;
} rt;
};
diff --git a/src/game.cpp b/src/game.cpp
index 3c39018..2edea8c 100644
--- a/src/game.cpp
+++ b/src/game.cpp
@@ -17,6 +17,7 @@
#include "player-input.hpp"
#include "plugins.hpp"
#include "project.hpp"
+#include "scene.hpp"
#include "static-ui.hpp"
#include "thread-pool.hpp"
#include "window.hpp"
@@ -162,7 +163,7 @@ bool FindFirstInstanceHandle(void *handle, void *mapping) {
InstMapping *inst_mapping = reinterpret_cast<InstMapping *>(mapping);
return inst_mapping->origHandle == *reinterpret_cast<InstanceHandle *>(handle);
}
-void DeserializeCamera(PkeLevel *level, std::istream &stream) {
+void DeserializeCamera(pke_scene *scene, std::istream &stream) {
PkeCamera cam{};
InstanceHandle instanceHandle = InstanceHandle_MAX;
InstanceHandle targetInstanceHandle = InstanceHandle_MAX;
@@ -191,7 +192,7 @@ void DeserializeCamera(PkeLevel *level, std::istream &stream) {
rCam.view = cam.view;
rCam.isPrimary = cam.isPrimary;
rCam.phys.targetInstHandle = targetInstanceHandle;
- PkeLevel_RegisterCamera(level->levelHandle, rCam.camHandle);
+ pke_scene_register_camera(scene->scene_handle, rCam.camHandle);
if (targetInstanceIndex > -1) {
PkeCamera_TargetInstance(rCam.camHandle, ECS_GetInstance(loadFileInstanceMappings[targetInstanceIndex].newInstHandle));
}
@@ -476,21 +477,27 @@ void Game_SaveSceneFile(const char *sceneFilePath) {
}
}
-void Game_LoadSceneFile(PkeLevel *level, const char *sceneFilePath) {
+void Game_LoadSceneFile(const char *sceneFilePath) {
std::ifstream f(sceneFilePath);
if (!f.is_open()) {
- fprintf(stderr, "Failed to load requested scene file: %s\n", sceneFilePath);
+ fprintf(stderr, "Failed to load requested scene file: '%s'\n", sceneFilePath);
return;
}
+ // TODO scene name is in the file?
+ pke_scene *scn = pke_scene_get_by_name(sceneFilePath);
+ if (scn == nullptr) {
+ scn = pke_scene_create(sceneFilePath);
+ }
+
memset(readLine, '\0', readLineLength);
while (f.getline(readLine, readLineLength)) {
if (strcmp(PKE_FILE_OBJ_INSTANCE, readLine) == 0) {
- DeserializeInstance(level, f);
+ DeserializeInstance(scn, f);
continue;
}
if (strcmp(PKE_FILE_OBJ_CAMERA, readLine) == 0) {
- DeserializeCamera(level, f);
+ DeserializeCamera(scn, f);
continue;
}
}
@@ -539,17 +546,18 @@ void Game_RecordImGui() {
void Game_Tick(double delta) {
pk_bucket_reset(pkeSettings.mem.bkt);
- // TODO this should be removed in favor of storing the scene details inside a level definition
- if (pkeSettings.rt.shouldLoadScene && pkeSettings.rt.sceneName) {
- pkeSettings.rt.shouldLoadScene = false;
- if (pkeSettings.rt.activeLevel != LevelHandle_MAX) {
- pkeSettings.rt.previousLevel = pkeSettings.rt.activeLevel;
- }
- pkeSettings.rt.nextLevel = PkeLevel_Create(pkeSettings.rt.sceneName);
- }
if (pkeSettings.rt.nextLevel != LevelHandle_MAX) {
// TODO async this
- Game_LoadSceneFile(PkeLevel_Get(pkeSettings.rt.nextLevel), pkeSettings.rt.sceneName);
+ PkeLevel *lvl = PkeLevel_Get(pkeSettings.rt.activeLevel);
+ for (uint32_t i = 0; i < lvl->scene_instances.next; ++i) {
+ // TODO can't instantiate a scene that hasn't been loaded yet
+ /*
+ struct pke_scene *scene = pke_scene_get_by_handle(lvl->scene_instances[i].scene_handle);
+ if (scene != nullptr) {
+ Game_LoadSceneFile(scene->name);
+ }
+ */
+ }
pkeSettings.rt.activeLevel = pkeSettings.rt.nextLevel;
pkeSettings.rt.nextLevel = LevelHandle_MAX;
}
@@ -593,6 +601,7 @@ void Game_Main(PKEWindowProperties windowProps, const char *executablePath) {
Physics_Init();
PkeCamera_Init();
PkeLevel_Init();
+ pke_scene_master_init();
Game_Init();
CreateWindow(windowProps);
EntityType_Init();
@@ -610,6 +619,20 @@ void Game_Main(PKEWindowProperties windowProps, const char *executablePath) {
}
}
+ //
+ // at this point, everything is loaded or initialized.
+ //
+
+ // if we were passed only a scene name, create a faux level.
+ if (!pkeSettings.args.levelName && pkeSettings.args.sceneName) {
+ pkeSettings.rt.nextLevel = PkeLevel_Create("faux-level")->levelHandle;
+ PkeLevel *lvl = PkeLevel_Get(pkeSettings.rt.nextLevel);
+ scene_instance si{};
+ si.scene_handle = pke_scene_get_by_name(pkeSettings.args.sceneName)->scene_handle;
+ pk_arr_append_t(&lvl->scene_instances, si);
+ }
+
+ // TODO remove me: temp stuff for testing
pk_cstr test_text = cstring_to_pk_cstr("012\n3456789\tThe quick\r\nbrown fox jumped over the lazy dog.");
// pk_cstr test_text = cstring_to_pk_cstr("%+-*0123456789$");
// pk_cstr test_text = cstring_to_pk_cstr("$#");
@@ -634,6 +657,7 @@ void Game_Main(PKEWindowProperties windowProps, const char *executablePath) {
FontType_AddStringRender(FontTypeIndex{1}, pk_cstr_to_pk_str(&test_text2), &fr_set);
}
+ // TODO remove me: temp stuff for testing
pke_ui_box *ui_box = pke_ui_box_new_root();
ui_box->flags = PKE_UI_BOX_FLAG_POSITION_TYPE_DYNAMIC;
ui_box->pos_top_left_x = 0.1;
@@ -641,6 +665,7 @@ void Game_Main(PKEWindowProperties windowProps, const char *executablePath) {
ui_box->max_width = 0.8;
ui_box->max_height = 0.8;
+ // TODO remove me: temp stuff for testing
pke_ui_box *c_ui_box = pke_ui_box_new_child(ui_box);
c_ui_box->flags = PKE_UI_BOX_FLAG_POSITION_TYPE_STATIC;
c_ui_box->pos_top_left_x = 20;
@@ -747,6 +772,8 @@ void Game_Main(PKEWindowProperties windowProps, const char *executablePath) {
FontType_Teardown();
pke_ui_teardown();
PkeInput_Teardown();
+ pke_scene_master_teardown();
+ PkeLevel_Teardown();
PkeCamera_Teardown();
Physics_Teardown();
ECS_Teardown();
diff --git a/src/game.hpp b/src/game.hpp
index 95d010c..8476ff0 100644
--- a/src/game.hpp
+++ b/src/game.hpp
@@ -1,7 +1,6 @@
#ifndef PKE_GAME_HPP
#define PKE_GAME_HPP
-#include "level-types.hpp"
#include "window-types.hpp"
void Game_Main(PKEWindowProperties windowProps, const char *executablePath);
@@ -10,6 +9,6 @@ void Game_Tick(double delta);
void Game_Teardown();
void Game_RecordImGui();
void Game_SaveSceneFile(const char *);
-void Game_LoadSceneFile(PkeLevel *level, const char *);
+void Game_LoadSceneFile(const char *);
#endif /* PKE_GAME_HPP */
diff --git a/src/level-types.hpp b/src/level-types.hpp
index 4de0f76..75bc6eb 100644
--- a/src/level-types.hpp
+++ b/src/level-types.hpp
@@ -1,19 +1,26 @@
#ifndef PKE_LEVEL_TYPES_HPP
#define PKE_LEVEL_TYPES_HPP
-#include "pk.h"
-#include "camera.hpp"
#include "components.hpp"
+#include "vendor-glm-include.hpp"
+#include "pk.h"
-TypeSafeInt_constexpr(LevelHandle, uint16_t, 0xFFFF);
+struct scene_instance {
+ glm::vec3 pos; // TODO
+ glm::vec3 rot; // TODO
+ glm::vec3 scale; // TODO
+ SceneHandle scene_handle;
+};
-struct LvlCamArr : public pk_arr_t<CameraHandle>{};
+const uint8_t LEVEL_NAME_MAX_LEN = 16;
+#define pke_level_name_printf_format "%16s"
struct PkeLevel : public Entity_Base {
+ char *file_path = nullptr;
struct pk_membucket *bkt = nullptr;
- char name[16] = {'\0'};
+ char name[LEVEL_NAME_MAX_LEN] = {'\0'};
LevelHandle levelHandle = LevelHandle_MAX;
- LvlCamArr cameras;
+ pk_arr_t<scene_instance> scene_instances;
};
#endif /* PKE_LEVEL_TYPES_HPP */
diff --git a/src/level.cpp b/src/level.cpp
index 047363b..57faf80 100644
--- a/src/level.cpp
+++ b/src/level.cpp
@@ -1,87 +1,98 @@
#include "level.hpp"
-#include "camera.hpp"
+#include "bucketed-array.hpp"
#include "ecs.hpp"
#include "pk.h"
-LevelHandle nextHandle = LevelHandle{0};
-long levelCount = 0;
-constexpr long LEVEL_NAME_LENGTH = 16;
-PkeLevel LEVELS[MAX_LEVEL_COUNT];
+struct level_mstr {
+ BucketContainer<PkeLevel, LevelHandle> bc;
+} level_mstr;
+void PkeLevel_Init() {
+ Buckets_Init(level_mstr.bc);
+}
+
+void PkeLevel_Teardown() {
+ Buckets_Destroy(level_mstr.bc);
+}
+
+/*
PkeLevel *PkeLevel_Get_Inner(LevelHandle handle) {
- for (long i = 0; i < MAX_LEVEL_COUNT; ++i) {
- if (LEVELS[i].levelHandle == handle) {
- return &LEVELS[i];
- }
+ if ( || handle.itemIndex >= level_mstr.bc.limits.itemIndex || (handle.bucketIndex == handle.bucketIndex) {
}
- return nullptr;
+ return &level_mstr.bc.buckets[handle.bucketIndex][handle.itemIndex];
}
+*/
-void PkeLevel_Init() {
- for (long i = 0; i < MAX_LEVEL_COUNT; ++i) {
- new (&LEVELS[i]) PkeLevel{};
- pk_arr_reset(&LEVELS[i].cameras);
+PkeLevel *PkeLevel_Create(const char *levelName) {
+ NULL_CHAR_ARR(safe_name, LEVEL_NAME_MAX_LEN + 1);
+
+ size_t len = strlen(levelName);
+ size_t start = len <= (LEVEL_NAME_MAX_LEN - 1) ? 0 : len - (LEVEL_NAME_MAX_LEN - 1);
+ sprintf(safe_name, pke_level_name_printf_format, levelName + start);
+
+ PkeLevel *lvl = PkeLevel_GetByName(levelName);
+ if (lvl != nullptr) {
+ fprintf(stderr, "[PkeLevel_Create] Failed to create new level: name already exists.");
+ return nullptr;
}
-}
-LevelHandle PkeLevel_Create(const char *levelName) {
- assert(levelCount < MAX_LEVEL_COUNT && "only MAX_LEVEL_COUNT levels can be loaded at once");
- levelCount += 1;
+ LevelHandle level_handle = Buckets_NewHandle(level_mstr.bc);
+ if (level_handle == LevelHandle_MAX) {
+ fprintf(stderr, "[PkeLevel_Create] Failed to create new level handle from BucketContainer");
+ return nullptr;
+ }
- PkeLevel *lvl = PkeLevel_Get_Inner(LevelHandle_MAX);
- assert(lvl != nullptr && "max level count not reached, but failed to find a valid level slot");
+ lvl = &level_mstr.bc[level_handle];
ECS_CreateEntity(lvl);
if (lvl->bkt == nullptr) {
lvl->bkt = pk_bucket_create(levelName, PK_DEFAULT_BUCKET_SIZE, false);
}
- lvl->levelHandle = nextHandle;
- ++nextHandle;
- auto len = strlen(levelName);
- auto start = len <= (LEVEL_NAME_LENGTH - 1) ? 0 : len - (LEVEL_NAME_LENGTH - 1);
- strncpy(lvl->name, levelName + start, LEVEL_NAME_LENGTH);
- return lvl->levelHandle;
+ return lvl;
}
PkeLevel *PkeLevel_Get(LevelHandle handle) {
- return PkeLevel_Get_Inner(handle);
+ return &level_mstr.bc[handle];
}
-LevelHandle PkeLevel_GetHandle(const char *levelName) {
- char safeName[LEVEL_NAME_LENGTH];
+PkeLevel *PkeLevel_GetByName(const char *levelName) {
+ NULL_CHAR_ARR(safe_name, LEVEL_NAME_MAX_LEN + 1);
auto len = strlen(levelName);
- auto start = len <= (LEVEL_NAME_LENGTH - 1) ? 0 : len - (LEVEL_NAME_LENGTH - 1);
- strncpy(safeName, levelName + start, LEVEL_NAME_LENGTH);
- for (long i = 0; i < MAX_LEVEL_COUNT; ++i) {
- if (LEVELS[i].levelHandle != LevelHandle_MAX) {
- if (strcmp(safeName, LEVELS[i].name)) {
- return LEVELS[i].levelHandle;
+ auto start = len <= (LEVEL_NAME_MAX_LEN - 1) ? 0 : len - (LEVEL_NAME_MAX_LEN - 1);
+ sprintf(safe_name, pke_level_name_printf_format, levelName + start);
+
+ pk_handle_bucket_index_T b;
+ pk_handle_item_index_T i, ii;
+ for (b = 0; b < level_mstr.bc.pkeHandle.bucketIndex; ++b) {
+ ii = level_mstr.bc.pkeHandle.bucketIndex == b ? level_mstr.bc.pkeHandle.itemIndex : level_mstr.bc.limits.itemIndex;
+ for (i = 0; i < ii; ++i) {
+ if (memcmp(safe_name, level_mstr.bc.buckets[b][i].name, LEVEL_NAME_MAX_LEN) == 0) {
+ return &level_mstr.bc.buckets[b][i];
}
}
}
- return LevelHandle_MAX;
+ return nullptr;
+}
+
+pk_handle_bucket_index_T pke_level_get_bucket_count() {
+ return level_mstr.bc.pkeHandle.bucketIndex + 1;
}
-void PkeLevel_RegisterCamera(LevelHandle levelHandle, CameraHandle cameraHandle) {
- assert(levelHandle != LevelHandle_MAX);
- assert(cameraHandle != CameraHandle_MAX);
- PkeLevel *lvl = PkeLevel_Get_Inner(levelHandle);
- assert(lvl != nullptr && "Failed to find level by requested LevelHandle");
- pk_arr_append(&lvl->cameras, &cameraHandle);
+struct PkeLevel *pke_level_get_levels(pk_handle_bucket_index_T bucket_index, pk_handle_item_index_T *item_count) {
+ assert(bucket_index <= level_mstr.bc.pkeHandle.bucketIndex);
+ assert(item_count != nullptr);
+ *item_count = bucket_index == level_mstr.bc.pkeHandle.bucketIndex ? level_mstr.bc.pkeHandle.itemIndex : level_mstr.bc.limits.itemIndex;
+ return level_mstr.bc.buckets[bucket_index];
}
void PkeLevel_Remove(LevelHandle handle) {
- PkeLevel *lvl = PkeLevel_Get_Inner(handle);
- assert(lvl != nullptr && "Failed to find level to remove by requested LevelHandle");
- levelCount -= 1;
+ PkeLevel *lvl = &level_mstr.bc[handle];
+ assert(lvl == nullptr && "Failed to find level to remove by requested LevelHandle");
+ // TODO mark bucket slot as open
ECS_MarkForRemoval(lvl);
- for (long i = 0; i < lvl->cameras.next; ++i) {
- PkeCamera_Destroy(lvl->cameras[i]);
- }
- pk_arr_reset(&lvl->cameras);
pk_bucket_reset(lvl->bkt);
lvl->levelHandle = LevelHandle_MAX;
}
diff --git a/src/level.hpp b/src/level.hpp
index 58cdcd3..1a07914 100644
--- a/src/level.hpp
+++ b/src/level.hpp
@@ -3,14 +3,13 @@
#include "level-types.hpp"
-constexpr long MAX_LEVEL_COUNT = 16;
-extern PkeLevel LEVELS[];
-
void PkeLevel_Init();
-LevelHandle PkeLevel_Create(const char *levelName);
+void PkeLevel_Teardown();
+PkeLevel *PkeLevel_Create(const char *levelName);
PkeLevel *PkeLevel_Get(LevelHandle handle);
-LevelHandle PkeLevel_GetHandle(const char *levelName);
-void PkeLevel_RegisterCamera(LevelHandle levelHandle, CameraHandle cameraHandle);
+PkeLevel *PkeLevel_GetByName(const char *levelName);
+pk_handle_bucket_index_T pke_level_get_bucket_count();
+struct PkeLevel *pke_level_get_levels(pk_handle_bucket_index_T bucket_index, pk_handle_item_index_T *item_count);
void PkeLevel_Remove(LevelHandle handle);
#endif /* PKE_LEVEL_HPP */
diff --git a/src/scene-types.hpp b/src/scene-types.hpp
new file mode 100644
index 0000000..655c621
--- /dev/null
+++ b/src/scene-types.hpp
@@ -0,0 +1,18 @@
+#ifndef PKE_SCENE_TYPES_HPP
+#define PKE_SCENE_TYPES_HPP
+
+#include "pk.h"
+#include "components.hpp"
+#include "camera.hpp"
+
+const uint8_t SCENE_NAME_MAX_LEN = 16;
+#define pke_scene_name_printf_format "%16s"
+
+struct pke_scene : public Entity_Base {
+ pk_str file_path = {};
+ char name[SCENE_NAME_MAX_LEN] = {'\0'};
+ SceneHandle scene_handle = SceneHandle_MAX;
+ pk_arr_t<CameraHandle> cameras;
+};
+
+#endif /* PKE_SCENE_TYPES_HPP */
diff --git a/src/scene.cpp b/src/scene.cpp
new file mode 100644
index 0000000..0e5a22c
--- /dev/null
+++ b/src/scene.cpp
@@ -0,0 +1,96 @@
+
+#include "scene.hpp"
+#include "bucketed-array.hpp"
+#include "ecs.hpp"
+#include <cstring>
+
+struct pke_scene_master {
+ BucketContainer<pke_scene, SceneHandle> bc;
+} scene_mstr;
+
+void pke_scene_master_init() {
+ Buckets_Init(scene_mstr.bc);
+}
+
+void pke_scene_master_teardown() {
+ Buckets_Destroy(scene_mstr.bc);
+}
+
+pke_scene *pke_scene_create(const char *scene_name) {
+ struct pke_scene *scene = pke_scene_get_by_name(scene_name);
+ if (scene != nullptr) {
+ fprintf(stderr, "[pke_scene_create] failed to create scene: pke_scene::name already in use.");
+ return nullptr;
+ }
+ SceneHandle scene_handle = Buckets_NewHandle(scene_mstr.bc);
+ if (scene_handle == SceneHandle_MAX) {
+ fprintf(stderr, "[pke_scene_create] failed to get new scene handle from BucketContainer.");
+ return nullptr;
+ }
+ scene = &scene_mstr.bc.buckets[scene_handle.bucketIndex][scene_handle.itemIndex];
+ ECS_CreateEntity(scene);
+
+ scene->scene_handle = scene_handle;
+ size_t offset = (strlen(scene_name) > SCENE_NAME_MAX_LEN ? strlen(scene_name) - SCENE_NAME_MAX_LEN : 0);
+ for (int i = 0; i < SCENE_NAME_MAX_LEN; ++i) {
+ scene->name[i] = scene_name[i + offset];
+ }
+ return scene;
+}
+
+struct pke_scene *pke_scene_get_by_handle(SceneHandle scene_handle) {
+ return &scene_mstr.bc.buckets[scene_handle.bucketIndex][scene_handle.itemIndex];
+}
+
+struct pke_scene *pke_scene_get_by_name(const char *scene_name) {
+ assert(scene_name != nullptr);
+ NULL_CHAR_ARR(safe_name, SCENE_NAME_MAX_LEN + 1);
+ strncpy(safe_name, scene_name, SCENE_NAME_MAX_LEN + 1);
+ pk_handle_bucket_index_T b;
+ pk_handle_item_index_T i, ii;
+ for (b = 0; b < scene_mstr.bc.pkeHandle.bucketIndex + 1; ++b) {
+ ii = scene_mstr.bc.pkeHandle.bucketIndex == b ? scene_mstr.bc.pkeHandle.itemIndex : scene_mstr.bc.limits.itemIndex;
+ for (i = 0; i < ii; ++i) {
+ if (memcmp(safe_name, scene_mstr.bc.buckets[b][i].name, PK_MIN(strlen(safe_name), SCENE_NAME_MAX_LEN)) == 0) {
+ return &scene_mstr.bc.buckets[b][i];
+ }
+ }
+ }
+ return nullptr;
+}
+
+pk_handle_bucket_index_T pke_scene_get_bucket_count() {
+ return scene_mstr.bc.pkeHandle.bucketIndex + 1;
+}
+
+struct pke_scene *pke_scene_get_scenes(pk_handle_bucket_index_T bucket_index, pk_handle_item_index_T *item_count) {
+ assert(bucket_index <= scene_mstr.bc.pkeHandle.bucketIndex);
+ assert(item_count != nullptr);
+ *item_count = bucket_index == scene_mstr.bc.pkeHandle.bucketIndex ? scene_mstr.bc.pkeHandle.itemIndex : scene_mstr.bc.limits.itemIndex;
+ return scene_mstr.bc.buckets[bucket_index];
+}
+
+void pke_scene_remove(SceneHandle handle) {
+ pke_scene *scn = pke_scene_get_by_handle(handle);
+ assert(scn != nullptr && "[pke_scene_remove] Failed to find scene by requested SceneHandle");
+ // TODO mark bucket slot as open
+ ECS_MarkForRemoval(scn);
+ for (long i = 0; i < scn->cameras.next; ++i) {
+ PkeCamera_Destroy(scn->cameras[i]);
+ }
+ if (scn->file_path.reserved > 0) {
+ pk_delete<char>(scn->file_path.val, scn->file_path.reserved);
+ }
+ scn->file_path = {};
+ scn->name[0] = '\0';
+ scn->scene_handle = SceneHandle_MAX;
+ pk_arr_reset(&scn->cameras);
+}
+
+void pke_scene_register_camera(SceneHandle scene_handle, CameraHandle cameraHandle) {
+ assert(scene_handle != SceneHandle_MAX);
+ assert(cameraHandle != CameraHandle_MAX);
+ pke_scene *scene = pke_scene_get_by_handle(scene_handle);
+ assert(scene != nullptr && "Failed to find scene by requested SceneHandle");
+ pk_arr_append(&scene->cameras, &cameraHandle);
+}
diff --git a/src/scene.hpp b/src/scene.hpp
new file mode 100644
index 0000000..46d406c
--- /dev/null
+++ b/src/scene.hpp
@@ -0,0 +1,20 @@
+#ifndef PKE_SCENE_HPP
+#define PKE_SCENE_HPP
+
+#include "pk.h"
+#include "scene-types.hpp"
+#include "camera.hpp"
+
+void pke_scene_master_init();
+void pke_scene_master_teardown();
+
+struct pke_scene *pke_scene_create(const char *scene_name);
+struct pke_scene *pke_scene_get_by_handle(SceneHandle scene_handle);
+struct pke_scene *pke_scene_get_by_name(const char *scene_name);
+pk_handle_bucket_index_T pke_scene_get_bucket_count();
+struct pke_scene *pke_scene_get_scenes(pk_handle_bucket_index_T bucket_index, pk_handle_item_index_T *item_count);
+void pke_scene_remove(SceneHandle handle);
+
+void pke_scene_register_camera(SceneHandle scene_handle, CameraHandle cameraHandle);
+
+#endif /* PKE_SCENE_HPP */