hush
This commit is contained in:
@@ -2,29 +2,76 @@
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "navmesh.hh"
|
||||
#include "collision.hh"
|
||||
#include "profiler.hh"
|
||||
#include "renderer.hh"
|
||||
#include "splashpack.hh"
|
||||
#include "luaapi.hh"
|
||||
|
||||
#include "lua.h"
|
||||
|
||||
using namespace psyqo::trig_literals;
|
||||
|
||||
using namespace psyqo::fixed_point_literals;
|
||||
|
||||
using namespace psxsplash;
|
||||
|
||||
void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData) {
|
||||
L.Init();
|
||||
|
||||
debug::Profiler::getInstance().initialize();
|
||||
|
||||
L.Reset();
|
||||
|
||||
// Initialize audio system
|
||||
m_audio.init();
|
||||
|
||||
// Register the Lua API
|
||||
LuaAPI::RegisterAll(L.getState(), this);
|
||||
|
||||
#ifdef PSXSPLASH_PROFILER
|
||||
debug::Profiler::getInstance().initialize();
|
||||
#endif
|
||||
|
||||
SplashpackSceneSetup sceneSetup;
|
||||
m_loader.LoadSplashpack(splashpackData, sceneSetup);
|
||||
|
||||
m_luaFiles = std::move(sceneSetup.luaFiles);
|
||||
m_gameObjects = std::move(sceneSetup.objects);
|
||||
m_navmeshes = std::move(sceneSetup.navmeshes);
|
||||
m_objectNames = std::move(sceneSetup.objectNames);
|
||||
m_bvh = sceneSetup.bvh; // Copy BVH for frustum culling
|
||||
m_worldCollision = sceneSetup.worldCollision; // World collision soup (v7+)
|
||||
m_navRegions = sceneSetup.navRegions; // Nav region system (v7+)
|
||||
m_playerNavRegion = m_navRegions.isLoaded() ? m_navRegions.getStartRegion() : NAV_NO_REGION;
|
||||
|
||||
// Scene type and render path
|
||||
m_sceneType = sceneSetup.sceneType;
|
||||
|
||||
// Room/portal data for interior scenes (v11+)
|
||||
m_rooms = sceneSetup.rooms;
|
||||
m_roomCount = sceneSetup.roomCount;
|
||||
m_portals = sceneSetup.portals;
|
||||
m_portalCount = sceneSetup.portalCount;
|
||||
m_roomTriRefs = sceneSetup.roomTriRefs;
|
||||
m_roomTriRefCount = sceneSetup.roomTriRefCount;
|
||||
|
||||
// Configure fog from splashpack data (v11+)
|
||||
if (sceneSetup.fogEnabled) {
|
||||
psxsplash::FogConfig fogCfg;
|
||||
fogCfg.enabled = true;
|
||||
fogCfg.color = {.r = sceneSetup.fogR, .g = sceneSetup.fogG, .b = sceneSetup.fogB};
|
||||
fogCfg.density = sceneSetup.fogDensity;
|
||||
Renderer::GetInstance().SetFog(fogCfg);
|
||||
} else {
|
||||
psxsplash::FogConfig fogCfg;
|
||||
fogCfg.enabled = false;
|
||||
Renderer::GetInstance().SetFog(fogCfg);
|
||||
}
|
||||
// Copy component arrays
|
||||
m_interactables = std::move(sceneSetup.interactables);
|
||||
|
||||
// Load audio clips into SPU RAM
|
||||
m_audioClipNames = std::move(sceneSetup.audioClipNames);
|
||||
for (size_t i = 0; i < sceneSetup.audioClips.size(); i++) {
|
||||
auto& clip = sceneSetup.audioClips[i];
|
||||
m_audio.loadClip((int)i, clip.adpcmData, clip.sizeBytes, clip.sampleRate, clip.loop);
|
||||
}
|
||||
|
||||
m_playerPosition = sceneSetup.playerStartPosition;
|
||||
|
||||
@@ -34,6 +81,49 @@ void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData) {
|
||||
|
||||
m_playerHeight = sceneSetup.playerHeight;
|
||||
|
||||
// Load movement parameters from splashpack (v8+)
|
||||
m_controls.setMoveSpeed(sceneSetup.moveSpeed);
|
||||
m_controls.setSprintSpeed(sceneSetup.sprintSpeed);
|
||||
m_playerRadius = (int32_t)sceneSetup.playerRadius.value;
|
||||
if (m_playerRadius == 0) m_playerRadius = PLAYER_RADIUS; // fallback to default
|
||||
m_jumpVelocityRaw = (int32_t)sceneSetup.jumpVelocity.value;
|
||||
int32_t gravityRaw = (int32_t)sceneSetup.gravity.value;
|
||||
m_gravityPerFrame = gravityRaw / 30; // Convert per-second² to per-frame velocity change
|
||||
if (m_gravityPerFrame == 0 && gravityRaw > 0) m_gravityPerFrame = 1; // Ensure nonzero
|
||||
m_velocityY = 0;
|
||||
m_isGrounded = true;
|
||||
m_lastFrameTime = 0;
|
||||
m_deltaFrames = 1;
|
||||
|
||||
// Initialize collision system
|
||||
m_collisionSystem.init();
|
||||
|
||||
// Register colliders from splashpack data
|
||||
for (size_t i = 0; i < sceneSetup.colliders.size(); i++) {
|
||||
SPLASHPACKCollider* collider = sceneSetup.colliders[i];
|
||||
if (collider == nullptr) continue;
|
||||
|
||||
// Convert fixed-point values from binary format to AABB
|
||||
AABB bounds;
|
||||
bounds.min.x.value = collider->minX;
|
||||
bounds.min.y.value = collider->minY;
|
||||
bounds.min.z.value = collider->minZ;
|
||||
bounds.max.x.value = collider->maxX;
|
||||
bounds.max.y.value = collider->maxY;
|
||||
bounds.max.z.value = collider->maxZ;
|
||||
|
||||
// Convert collision type
|
||||
CollisionType type = static_cast<CollisionType>(collider->collisionType);
|
||||
|
||||
// Register with collision system
|
||||
m_collisionSystem.registerCollider(
|
||||
collider->gameObjectIndex,
|
||||
bounds,
|
||||
type,
|
||||
collider->layerMask
|
||||
);
|
||||
}
|
||||
|
||||
// Load Lua files - order is important here. We need
|
||||
// to load the Lua files before we register the game objects,
|
||||
// as the game objects may reference Lua files by index.
|
||||
@@ -58,49 +148,470 @@ void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData) {
|
||||
}
|
||||
|
||||
void psxsplash::SceneManager::GameTick(psyqo::GPU &gpu) {
|
||||
LuaAPI::IncrementFrameCount();
|
||||
|
||||
// Delta-time measurement: count elapsed frames based on gpu timer
|
||||
// PS1 NTSC frame = ~33333 microseconds (30fps vsync)
|
||||
{
|
||||
uint32_t now = gpu.now();
|
||||
if (m_lastFrameTime != 0) {
|
||||
uint32_t elapsed = now - m_lastFrameTime;
|
||||
// 33333us per frame at 30fps. If >50000us, we dropped a frame.
|
||||
m_deltaFrames = (elapsed > 50000) ? 2 : 1;
|
||||
if (elapsed > 83000) m_deltaFrames = 3; // Two frames dropped
|
||||
}
|
||||
m_lastFrameTime = now;
|
||||
}
|
||||
|
||||
uint32_t renderingStart = gpu.now();
|
||||
auto& renderer = psxsplash::Renderer::GetInstance();
|
||||
renderer.Render(m_gameObjects);
|
||||
// Dispatch render path based on scene type.
|
||||
// Interior scenes (type 1) use room/portal occlusion; exterior scenes use BVH culling.
|
||||
if (m_sceneType == 1 && m_roomCount > 0 && m_rooms != nullptr) {
|
||||
// Get camera room from nav region system (authoritative) instead of AABB guessing.
|
||||
// NavRegion::roomIndex is set during export from the room each region belongs to.
|
||||
int camRoom = -1;
|
||||
if (m_navRegions.isLoaded() && m_playerNavRegion != NAV_NO_REGION) {
|
||||
uint8_t ri = m_navRegions.getRoomIndex(m_playerNavRegion);
|
||||
if (ri != 0xFF) camRoom = (int)ri;
|
||||
}
|
||||
renderer.RenderWithRooms(m_gameObjects, m_rooms, m_roomCount,
|
||||
m_portals, m_portalCount, m_roomTriRefs, camRoom);
|
||||
} else {
|
||||
renderer.RenderWithBVH(m_gameObjects, m_bvh);
|
||||
}
|
||||
gpu.pumpCallbacks();
|
||||
uint32_t renderingEnd = gpu.now();
|
||||
uint32_t renderingTime = renderingEnd - renderingStart;
|
||||
|
||||
psxsplash::debug::Profiler::getInstance().setSectionTime(psxsplash::debug::PROFILER_RENDERING, renderingTime);
|
||||
#ifdef PSXSPLASH_PROFILER
|
||||
psxsplash::debug::Profiler::getInstance().setSectionTime(psxsplash::debug::PROFILER_RENDERING, renderingTime);
|
||||
#endif
|
||||
|
||||
// Collision detection
|
||||
uint32_t collisionStart = gpu.now();
|
||||
int collisionCount = m_collisionSystem.detectCollisions();
|
||||
|
||||
// Process solid collisions - call OnCollision on BOTH objects
|
||||
const CollisionResult* results = m_collisionSystem.getResults();
|
||||
for (int i = 0; i < collisionCount; i++) {
|
||||
auto* objA = getGameObject(results[i].objectA);
|
||||
auto* objB = getGameObject(results[i].objectB);
|
||||
if (objA && objB) {
|
||||
L.OnCollision(objA, objB);
|
||||
L.OnCollision(objB, objA); // Call on both objects
|
||||
}
|
||||
}
|
||||
|
||||
// Process trigger events (enter/stay/exit)
|
||||
m_collisionSystem.processTriggerEvents(*this);
|
||||
|
||||
gpu.pumpCallbacks();
|
||||
uint32_t collisionEnd = gpu.now();
|
||||
|
||||
uint32_t luaStart = gpu.now();
|
||||
L.OnCollision(m_gameObjects[1], m_gameObjects[0]); // Example call, replace with actual logic
|
||||
// Lua update tick - call onUpdate for all registered objects with onUpdate handler
|
||||
for (auto* go : m_gameObjects) {
|
||||
if (go && go->isActive()) {
|
||||
L.OnUpdate(go, m_deltaFrames);
|
||||
}
|
||||
}
|
||||
gpu.pumpCallbacks();
|
||||
uint32_t luaEnd = gpu.now();
|
||||
uint32_t luaTime = luaEnd - luaStart;
|
||||
psxsplash::debug::Profiler::getInstance().setSectionTime(psxsplash::debug::PROFILER_LUA, luaTime);
|
||||
#ifdef PSXSPLASH_PROFILER
|
||||
psxsplash::debug::Profiler::getInstance().setSectionTime(psxsplash::debug::PROFILER_LUA, luaTime);
|
||||
#endif
|
||||
|
||||
// Update game systems
|
||||
processEnableDisableEvents();
|
||||
|
||||
|
||||
uint32_t controlsStart = gpu.now();
|
||||
m_controls.HandleControls(m_playerPosition, playerRotationX, playerRotationY, playerRotationZ, false, 1);
|
||||
|
||||
// Update button state tracking first
|
||||
m_controls.UpdateButtonStates();
|
||||
|
||||
// Update interaction system (checks for interact button press)
|
||||
updateInteractionSystem();
|
||||
|
||||
// Dispatch button events to all objects
|
||||
uint16_t pressed = m_controls.getButtonsPressed();
|
||||
uint16_t released = m_controls.getButtonsReleased();
|
||||
|
||||
if (pressed || released) {
|
||||
// Only iterate objects if there are button events
|
||||
for (auto* go : m_gameObjects) {
|
||||
if (!go || !go->isActive()) continue;
|
||||
|
||||
if (pressed) {
|
||||
// Dispatch press events for each pressed button
|
||||
for (int btn = 0; btn < 16; btn++) {
|
||||
if (pressed & (1 << btn)) {
|
||||
L.OnButtonPress(go, btn);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (released) {
|
||||
// Dispatch release events for each released button
|
||||
for (int btn = 0; btn < 16; btn++) {
|
||||
if (released & (1 << btn)) {
|
||||
L.OnButtonRelease(go, btn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save position BEFORE movement for collision detection
|
||||
psyqo::Vec3 oldPlayerPosition = m_playerPosition;
|
||||
|
||||
m_controls.HandleControls(m_playerPosition, playerRotationX, playerRotationY, playerRotationZ, freecam, m_deltaFrames);
|
||||
|
||||
// Jump input: Cross button triggers jump when grounded
|
||||
if (m_isGrounded && m_controls.wasButtonPressed(psyqo::AdvancedPad::Button::Cross)) {
|
||||
m_velocityY = -m_jumpVelocityRaw; // Negative = upward (PSX Y-down)
|
||||
m_isGrounded = false;
|
||||
}
|
||||
|
||||
gpu.pumpCallbacks();
|
||||
uint32_t controlsEnd = gpu.now();
|
||||
uint32_t controlsTime = controlsEnd - controlsStart;
|
||||
psxsplash::debug::Profiler::getInstance().setSectionTime(psxsplash::debug::PROFILER_CONTROLS, controlsTime);
|
||||
#ifdef PSXSPLASH_PROFILER
|
||||
psxsplash::debug::Profiler::getInstance().setSectionTime(psxsplash::debug::PROFILER_CONTROLS, controlsTime);
|
||||
#endif
|
||||
|
||||
uint32_t navmeshStart = gpu.now();
|
||||
if (!freecam) {
|
||||
psxsplash::ComputeNavmeshPosition(m_playerPosition, *m_navmeshes[0],
|
||||
static_cast<psyqo::FixedPoint<12>>(m_playerHeight));
|
||||
// Priority: WorldCollision + NavRegions (v7) > NavGrid (v5) > Legacy Navmesh
|
||||
if (m_worldCollision.isLoaded()) {
|
||||
// Move-and-slide against world geometry (XZ walls only)
|
||||
psyqo::Vec3 slid = m_worldCollision.moveAndSlide(
|
||||
oldPlayerPosition, m_playerPosition, m_playerRadius, 0xFF);
|
||||
|
||||
|
||||
m_playerPosition.x = slid.x;
|
||||
m_playerPosition.z = slid.z;
|
||||
|
||||
// Apply gravity: velocity changes each frame
|
||||
for (int f = 0; f < m_deltaFrames; f++) {
|
||||
m_velocityY += m_gravityPerFrame;
|
||||
}
|
||||
|
||||
// Apply vertical velocity to position
|
||||
// velocityY is in fp12 per-second; convert per-frame: pos += vel / 30
|
||||
int32_t posYDelta = (m_velocityY * m_deltaFrames) / 30;
|
||||
m_playerPosition.y.value += posYDelta;
|
||||
|
||||
// Resolve floor Y from nav regions if available
|
||||
if (m_navRegions.isLoaded()) {
|
||||
uint16_t prevRegion = m_playerNavRegion;
|
||||
int32_t px = m_playerPosition.x.value;
|
||||
int32_t pz = m_playerPosition.z.value;
|
||||
int32_t floorY = m_navRegions.resolvePosition(
|
||||
px, pz, m_playerNavRegion);
|
||||
|
||||
|
||||
if (m_playerNavRegion != NAV_NO_REGION) {
|
||||
m_playerPosition.x.value = px;
|
||||
m_playerPosition.z.value = pz;
|
||||
|
||||
// Ground (feet) position in PSX coords:
|
||||
// Camera is at position.y, feet are at position.y + playerHeight
|
||||
// (Y-down: larger Y = lower)
|
||||
int32_t cameraAtFloor = floorY - m_playerHeight.raw();
|
||||
|
||||
if (m_playerPosition.y.value >= cameraAtFloor) {
|
||||
// Player is at or below floor — snap to ground
|
||||
m_playerPosition.y.value = cameraAtFloor;
|
||||
m_velocityY = 0;
|
||||
m_isGrounded = true;
|
||||
} else {
|
||||
// Player is above floor (jumping/airborne)
|
||||
m_isGrounded = false;
|
||||
}
|
||||
} else {
|
||||
// Off all nav regions — revert to old position
|
||||
m_playerPosition = oldPlayerPosition;
|
||||
m_playerNavRegion = prevRegion;
|
||||
m_velocityY = 0;
|
||||
m_isGrounded = true;
|
||||
}
|
||||
} else {
|
||||
// Ground trace fallback (no nav regions)
|
||||
int32_t groundY;
|
||||
int32_t groundNormalY;
|
||||
uint8_t surfFlags;
|
||||
if (m_worldCollision.groundTrace(m_playerPosition,
|
||||
4096 * 4, // max 4 units down
|
||||
groundY, groundNormalY, surfFlags, 0xFF)) {
|
||||
int32_t cameraAtFloor = groundY - m_playerHeight.raw();
|
||||
if (m_playerPosition.y.value >= cameraAtFloor) {
|
||||
m_playerPosition.y.value = cameraAtFloor;
|
||||
m_velocityY = 0;
|
||||
m_isGrounded = true;
|
||||
} else {
|
||||
m_isGrounded = false;
|
||||
}
|
||||
} else {
|
||||
m_playerPosition = oldPlayerPosition;
|
||||
m_velocityY = 0;
|
||||
m_isGrounded = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Ceiling check: if jumping upward, check for ceiling collision
|
||||
if (m_velocityY < 0 && m_worldCollision.isLoaded()) {
|
||||
int32_t ceilingY;
|
||||
if (m_worldCollision.ceilingTrace(m_playerPosition,
|
||||
m_playerHeight.raw(), ceilingY, 0xFF)) {
|
||||
// Hit a ceiling — stop upward velocity
|
||||
m_velocityY = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
gpu.pumpCallbacks();
|
||||
uint32_t navmeshEnd = gpu.now();
|
||||
uint32_t navmeshTime = navmeshEnd - navmeshStart;
|
||||
psxsplash::debug::Profiler::getInstance().setSectionTime(psxsplash::debug::PROFILER_NAVMESH, navmeshTime);
|
||||
#ifdef PSXSPLASH_PROFILER
|
||||
psxsplash::debug::Profiler::getInstance().setSectionTime(psxsplash::debug::PROFILER_NAVMESH, navmeshTime);
|
||||
#endif
|
||||
|
||||
m_currentCamera.SetPosition(static_cast<psyqo::FixedPoint<12>>(m_playerPosition.x),
|
||||
static_cast<psyqo::FixedPoint<12>>(m_playerPosition.y),
|
||||
static_cast<psyqo::FixedPoint<12>>(m_playerPosition.z));
|
||||
m_currentCamera.SetRotation(playerRotationX, playerRotationY, playerRotationZ);
|
||||
|
||||
psxsplash::debug::Profiler::getInstance().dumpToTTY();
|
||||
// Process pending scene transitions (at end of frame)
|
||||
processPendingSceneLoad();
|
||||
}
|
||||
|
||||
// Trigger event callbacks
|
||||
void psxsplash::SceneManager::fireTriggerEnter(uint16_t triggerObjIdx, uint16_t otherObjIdx) {
|
||||
auto* trigger = getGameObject(triggerObjIdx);
|
||||
auto* other = getGameObject(otherObjIdx);
|
||||
if (trigger && other) {
|
||||
L.OnTriggerEnter(trigger, other);
|
||||
}
|
||||
}
|
||||
|
||||
void psxsplash::SceneManager::fireTriggerStay(uint16_t triggerObjIdx, uint16_t otherObjIdx) {
|
||||
auto* trigger = getGameObject(triggerObjIdx);
|
||||
auto* other = getGameObject(otherObjIdx);
|
||||
if (trigger && other) {
|
||||
L.OnTriggerStay(trigger, other);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
void psxsplash::SceneManager::fireTriggerExit(uint16_t triggerObjIdx, uint16_t otherObjIdx) {
|
||||
auto* trigger = getGameObject(triggerObjIdx);
|
||||
auto* other = getGameObject(otherObjIdx);
|
||||
if (trigger && other) {
|
||||
L.OnTriggerExit(trigger, other);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// INTERACTION SYSTEM
|
||||
// ============================================================================
|
||||
|
||||
void psxsplash::SceneManager::updateInteractionSystem() {
|
||||
// Get interact button state - Cross button by default
|
||||
auto interactButton = psyqo::AdvancedPad::Button::Cross;
|
||||
bool buttonPressed = m_controls.wasButtonPressed(interactButton);
|
||||
|
||||
if (!buttonPressed) return; // Early out if no interaction attempt
|
||||
|
||||
// Player position for distance check
|
||||
psyqo::FixedPoint<12> playerX = static_cast<psyqo::FixedPoint<12>>(m_playerPosition.x);
|
||||
psyqo::FixedPoint<12> playerY = static_cast<psyqo::FixedPoint<12>>(m_playerPosition.y);
|
||||
psyqo::FixedPoint<12> playerZ = static_cast<psyqo::FixedPoint<12>>(m_playerPosition.z);
|
||||
|
||||
// Find closest interactable in range
|
||||
Interactable* closest = nullptr;
|
||||
psyqo::FixedPoint<12> closestDistSq;
|
||||
closestDistSq.value = 0x7FFFFFFF; // Max positive value
|
||||
|
||||
for (auto* interactable : m_interactables) {
|
||||
if (!interactable || !interactable->canInteract()) continue;
|
||||
|
||||
// Check if object is active
|
||||
auto* go = getGameObject(interactable->gameObjectIndex);
|
||||
if (!go || !go->isActive()) continue;
|
||||
|
||||
// Calculate distance squared
|
||||
psyqo::FixedPoint<12> dx = playerX - interactable->offsetX - go->position.x;
|
||||
psyqo::FixedPoint<12> dy = playerY - interactable->offsetY - go->position.y;
|
||||
psyqo::FixedPoint<12> dz = playerZ - interactable->offsetZ - go->position.z;
|
||||
|
||||
psyqo::FixedPoint<12> distSq = dx * dx + dy * dy + dz * dz;
|
||||
|
||||
// Check if in range and closer than current closest
|
||||
if (distSq <= interactable->radiusSquared && distSq < closestDistSq) {
|
||||
closest = interactable;
|
||||
closestDistSq = distSq;
|
||||
}
|
||||
}
|
||||
|
||||
// Interact with closest
|
||||
if (closest != nullptr) {
|
||||
triggerInteraction(getGameObject(closest->gameObjectIndex));
|
||||
closest->triggerCooldown();
|
||||
}
|
||||
}
|
||||
|
||||
void psxsplash::SceneManager::triggerInteraction(GameObject* interactable) {
|
||||
if (!interactable) return;
|
||||
L.OnInteract(interactable);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// ENABLE/DISABLE SYSTEM
|
||||
// ============================================================================
|
||||
|
||||
void psxsplash::SceneManager::setObjectActive(GameObject* go, bool active) {
|
||||
if (!go) return;
|
||||
|
||||
bool wasActive = go->isActive();
|
||||
if (wasActive == active) return; // No change
|
||||
|
||||
go->setActive(active);
|
||||
|
||||
// Fire appropriate event
|
||||
if (active) {
|
||||
L.OnEnable(go);
|
||||
} else {
|
||||
L.OnDisable(go);
|
||||
}
|
||||
}
|
||||
|
||||
void psxsplash::SceneManager::processEnableDisableEvents() {
|
||||
// Process any pending enable/disable flags (for batched operations)
|
||||
for (auto* go : m_gameObjects) {
|
||||
if (!go) continue;
|
||||
|
||||
if (go->isPendingEnable()) {
|
||||
go->setPendingEnable(false);
|
||||
if (!go->isActive()) {
|
||||
go->setActive(true);
|
||||
L.OnEnable(go);
|
||||
}
|
||||
}
|
||||
|
||||
if (go->isPendingDisable()) {
|
||||
go->setPendingDisable(false);
|
||||
if (go->isActive()) {
|
||||
go->setActive(false);
|
||||
L.OnDisable(go);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SCENE LOADING (PCdrv multi-scene)
|
||||
// ============================================================================
|
||||
|
||||
void psxsplash::SceneManager::requestSceneLoad(int sceneIndex) {
|
||||
if (sceneIndex == m_currentSceneIndex) return;
|
||||
m_pendingSceneIndex = sceneIndex;
|
||||
}
|
||||
|
||||
void psxsplash::SceneManager::processPendingSceneLoad() {
|
||||
if (m_pendingSceneIndex < 0) return;
|
||||
|
||||
int targetIndex = m_pendingSceneIndex;
|
||||
m_pendingSceneIndex = -1;
|
||||
|
||||
// Build filename: scene_N.splashpack
|
||||
char filename[32];
|
||||
snprintf(filename, sizeof(filename), "scene_%d.splashpack", targetIndex);
|
||||
|
||||
// 1. Tear down EVERYTHING in the current scene first —
|
||||
// Lua VM, vector backing storage, audio. This returns as much
|
||||
// heap memory as possible before any new allocation.
|
||||
clearScene();
|
||||
|
||||
// 2. Free old splashpack data BEFORE loading the new one.
|
||||
// This avoids having both scene buffers in the heap simultaneously,
|
||||
// which is the primary source of fragmentation that prevents
|
||||
// the Lua compiler from finding large contiguous blocks.
|
||||
if (m_currentSceneData) {
|
||||
SceneLoader::FreeFile(m_currentSceneData);
|
||||
m_currentSceneData = nullptr;
|
||||
}
|
||||
|
||||
// 3. Allocate new scene data (heap is now maximally consolidated)
|
||||
int fileSize = 0;
|
||||
uint8_t* newData = SceneLoader::LoadFile(filename, fileSize);
|
||||
if (!newData) return;
|
||||
|
||||
m_currentSceneData = newData;
|
||||
m_currentSceneIndex = targetIndex;
|
||||
|
||||
// 4. Initialize with new data (creates fresh Lua VM inside)
|
||||
InitializeScene(newData);
|
||||
}
|
||||
|
||||
void psxsplash::SceneManager::clearScene() {
|
||||
// 1. Shut down the Lua VM first — frees ALL Lua-allocated memory
|
||||
// (bytecode, strings, tables, registry) in one shot via lua_close.
|
||||
L.Shutdown();
|
||||
|
||||
// 2. Free vector BACKING STORAGE (not just contents).
|
||||
// clear() only sets size=0 but keeps the allocated capacity.
|
||||
// swap-with-empty releases the heap blocks so they can be coalesced.
|
||||
{ eastl::vector<GameObject*> tmp; tmp.swap(m_gameObjects); }
|
||||
{ eastl::vector<LuaFile*> tmp; tmp.swap(m_luaFiles); }
|
||||
{ eastl::vector<const char*> tmp; tmp.swap(m_objectNames); }
|
||||
{ eastl::vector<const char*> tmp; tmp.swap(m_audioClipNames); }
|
||||
{ eastl::vector<Interactable*> tmp; tmp.swap(m_interactables); }
|
||||
|
||||
// 3. Reset hardware / subsystems
|
||||
m_audio.reset(); // Free SPU RAM and stop all voices
|
||||
m_collisionSystem.init(); // Re-init collision system
|
||||
// BVH, WorldCollision, and NavRegions will be overwritten by next load
|
||||
|
||||
// Reset room/portal pointers (they point into splashpack data which is being freed)
|
||||
m_rooms = nullptr;
|
||||
m_roomCount = 0;
|
||||
m_portals = nullptr;
|
||||
m_portalCount = 0;
|
||||
m_roomTriRefs = nullptr;
|
||||
m_roomTriRefCount = 0;
|
||||
m_sceneType = 0;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// OBJECT NAME LOOKUP
|
||||
// ============================================================================
|
||||
|
||||
// Inline streq (no libc on bare-metal PS1)
|
||||
static bool name_eq(const char* a, const char* b) {
|
||||
while (*a && *b) { if (*a++ != *b++) return false; }
|
||||
return *a == *b;
|
||||
}
|
||||
|
||||
psxsplash::GameObject* psxsplash::SceneManager::findObjectByName(const char* name) const {
|
||||
if (!name || m_objectNames.empty()) return nullptr;
|
||||
for (size_t i = 0; i < m_objectNames.size() && i < m_gameObjects.size(); i++) {
|
||||
if (m_objectNames[i] && name_eq(m_objectNames[i], name)) {
|
||||
return m_gameObjects[i];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int psxsplash::SceneManager::findAudioClipByName(const char* name) const {
|
||||
if (!name || m_audioClipNames.empty()) return -1;
|
||||
for (size_t i = 0; i < m_audioClipNames.size(); i++) {
|
||||
if (m_audioClipNames[i] && name_eq(m_audioClipNames[i], name)) {
|
||||
return static_cast<int>(i);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user