Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

- Updated SDL2 to SDL3

- Swapped LuaJIT to the [WohlSoft fork](https://github.com/WohlSoft/LuaJIT). Vendored from [dfcb8651](https://github.com/WohlSoft/LuaJIT/commit/dfcb8651).

- LuaJIT is now built from source on Windows as part of the solution, instead of shipping prebuilt libraries.

</details>

<details><summary><b>Fixed</b></summary>
Expand Down Expand Up @@ -264,6 +268,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

- Fixed an issue where if the first objects in the buy cart are items instead of an actor, they would be added to the first actor's inventory- even if it was an actor without an inventory (i.e a crab)

- Fixed a startup hang on Linux and macOS where a message box could deadlock, along with a `file_time_type` build error on those platforms.

- Fixed an issue where destroying a `SoundContainer` while it was still playing could leave dangling sound channels, which could cause audio glitches or a crash.

- Fixed a divide-by-zero on `AtomGroup` segments with no steps, which could feed bad values into the physics.

- Fixed a couple of uninitialized values (Atom step state and the simulation frame counter) that could cause inconsistent behavior between runs.

</details>

<details><summary><b>Removed</b></summary>
Expand Down
12 changes: 10 additions & 2 deletions Data/Base.rte/AI/HumanBehaviors.lua
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,11 @@ function HumanBehaviors.WeaponSearch(AI, Owner, Abort)
end

AI.PickupHD = nil;
table.sort(devicesToPickUp, function(A,B) return A.score < B.score end);
-- Tie-break on deviceId so ordering is stable when scores collide.
table.sort(devicesToPickUp, function(A,B)
if A.score ~= B.score then return A.score < B.score end
return A.deviceId < B.deviceId
end);
for _, deviceToPickupEntry in ipairs(devicesToPickUp) do
local device = MovableMan:FindObjectByUniqueID(deviceToPickupEntry.deviceId);
if MovableMan:ValidMO(device) and device:IsDevice() then
Expand Down Expand Up @@ -767,7 +771,11 @@ function HumanBehaviors.ToolSearch(AI, Owner, Abort)
end

AI.PickupHD = nil;
table.sort(devicesToPickUp, function(A,B) return A.score < B.score end); -- sort the items in order of discounted distance
-- Tie-break on deviceId so ordering is stable when scores collide.
table.sort(devicesToPickUp, function(A,B)
if A.score ~= B.score then return A.score < B.score end
return A.deviceId < B.deviceId
end); -- sort the items in order of discounted distance
for _, deviceToPickupEntry in ipairs(devicesToPickUp) do
local device = MovableMan:FindObjectByUniqueID(deviceToPickupEntry.deviceId);
if MovableMan:ValidMO(device) and device:IsDevice() then
Expand Down
20 changes: 10 additions & 10 deletions RTEA.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@
<LanguageStandard>stdcpp20</LanguageStandard>
<IntrinsicFunctions>true</IntrinsicFunctions>
<OmitDefaultLibName>false</OmitDefaultLibName>
<FloatingPointModel>Fast</FloatingPointModel>
<FloatingPointModel>Precise</FloatingPointModel>
<ConformanceMode>true</ConformanceMode>
<AddCodeAfterSourceInclude>#undef GetClassName</AddCodeAfterSourceInclude>
<IncludeInUnityFile>false</IncludeInUnityFile>
Expand Down Expand Up @@ -232,7 +232,7 @@
<LanguageStandard>stdcpp20</LanguageStandard>
<IntrinsicFunctions>true</IntrinsicFunctions>
<OmitDefaultLibName>false</OmitDefaultLibName>
<FloatingPointModel>Fast</FloatingPointModel>
<FloatingPointModel>Precise</FloatingPointModel>
<ConformanceMode>true</ConformanceMode>
<IncludeInUnityFile>false</IncludeInUnityFile>
<AddCodeAfterSourceInclude>#undef GetClassName</AddCodeAfterSourceInclude>
Expand Down Expand Up @@ -286,7 +286,7 @@
<LanguageStandard>stdcpp20</LanguageStandard>
<IntrinsicFunctions>true</IntrinsicFunctions>
<OmitDefaultLibName>false</OmitDefaultLibName>
<FloatingPointModel>Fast</FloatingPointModel>
<FloatingPointModel>Precise</FloatingPointModel>
<ConformanceMode>true</ConformanceMode>
<IncludeInUnityFile>false</IncludeInUnityFile>
<AddCodeAfterSourceInclude>#undef GetClassName</AddCodeAfterSourceInclude>
Expand Down Expand Up @@ -339,7 +339,7 @@
<LanguageStandard>stdcpp20</LanguageStandard>
<IntrinsicFunctions>true</IntrinsicFunctions>
<OmitDefaultLibName>false</OmitDefaultLibName>
<FloatingPointModel>Fast</FloatingPointModel>
<FloatingPointModel>Precise</FloatingPointModel>
<ConformanceMode>true</ConformanceMode>
<IncludeInUnityFile>false</IncludeInUnityFile>
<AddCodeAfterSourceInclude>#undef GetClassName</AddCodeAfterSourceInclude>
Expand Down Expand Up @@ -385,7 +385,7 @@
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<BufferSecurityCheck>false</BufferSecurityCheck>
<EnableEnhancedInstructionSet>StreamingSIMDExtensions2</EnableEnhancedInstructionSet>
<FloatingPointModel>Fast</FloatingPointModel>
<FloatingPointModel>Precise</FloatingPointModel>
<RuntimeTypeInfo>true</RuntimeTypeInfo>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level2</WarningLevel>
Expand Down Expand Up @@ -447,7 +447,7 @@
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<BufferSecurityCheck>false</BufferSecurityCheck>
<EnableEnhancedInstructionSet>StreamingSIMDExtensions2</EnableEnhancedInstructionSet>
<FloatingPointModel>Fast</FloatingPointModel>
<FloatingPointModel>Precise</FloatingPointModel>
<RuntimeTypeInfo>true</RuntimeTypeInfo>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level2</WarningLevel>
Expand Down Expand Up @@ -510,7 +510,7 @@
<BufferSecurityCheck>false</BufferSecurityCheck>
<EnableEnhancedInstructionSet>
</EnableEnhancedInstructionSet>
<FloatingPointModel>Fast</FloatingPointModel>
<FloatingPointModel>Precise</FloatingPointModel>
<RuntimeTypeInfo>true</RuntimeTypeInfo>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level2</WarningLevel>
Expand Down Expand Up @@ -573,7 +573,7 @@
<BufferSecurityCheck>false</BufferSecurityCheck>
<EnableEnhancedInstructionSet>
</EnableEnhancedInstructionSet>
<FloatingPointModel>Fast</FloatingPointModel>
<FloatingPointModel>Precise</FloatingPointModel>
<RuntimeTypeInfo>true</RuntimeTypeInfo>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level2</WarningLevel>
Expand Down Expand Up @@ -635,7 +635,7 @@
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<BufferSecurityCheck>false</BufferSecurityCheck>
<EnableEnhancedInstructionSet>StreamingSIMDExtensions2</EnableEnhancedInstructionSet>
<FloatingPointModel>Fast</FloatingPointModel>
<FloatingPointModel>Precise</FloatingPointModel>
<RuntimeTypeInfo>true</RuntimeTypeInfo>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level2</WarningLevel>
Expand Down Expand Up @@ -696,7 +696,7 @@
<BufferSecurityCheck>false</BufferSecurityCheck>
<EnableEnhancedInstructionSet>
</EnableEnhancedInstructionSet>
<FloatingPointModel>Fast</FloatingPointModel>
<FloatingPointModel>Precise</FloatingPointModel>
<RuntimeTypeInfo>true</RuntimeTypeInfo>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level2</WarningLevel>
Expand Down
9 changes: 5 additions & 4 deletions Source/Entities/AtomGroup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -354,8 +354,8 @@ float AtomGroup::Travel(Vector& position, Vector& velocity, Matrix& rotation, fl

HitData hitData;

// Thread locals for performance (avoid memory allocs)
thread_local std::unordered_map<MOID, std::vector<Atom*>> hitMOAtoms;
// std::map keeps hitMOAtoms iteration MOID-ascending so impulse accumulation order is deterministic.
thread_local std::map<MOID, std::vector<Atom*>> hitMOAtoms;
hitMOAtoms.clear();
thread_local std::vector<Atom*> hitTerrAtoms;
hitTerrAtoms.clear();
Expand Down Expand Up @@ -450,7 +450,7 @@ float AtomGroup::Travel(Vector& position, Vector& velocity, Matrix& rotation, fl
}

for (Atom* atom: m_Atoms) {
atom->SetStepRatio(static_cast<float>(atom->GetStepsLeft()) / static_cast<float>(stepsOnSeg));
atom->SetStepRatio(stepsOnSeg != 0 ? static_cast<float>(atom->GetStepsLeft()) / static_cast<float>(stepsOnSeg) : 0.0F);
}

// STEP LOOP ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -800,7 +800,8 @@ Vector AtomGroup::PushTravel(Vector& position, const Vector& velocity, float pus
// Thread locals for performance reasons (avoid memory allocs)
thread_local std::unordered_map<MOID, std::unordered_set<Atom*>> MOIgnoreMap;
MOIgnoreMap.clear();
thread_local std::unordered_map<MOID, std::vector<std::pair<Atom*, Vector>>> hitMOAtoms;
// std::map keeps hitMOAtoms iteration MOID-ascending so impulse accumulation order is deterministic.
thread_local std::map<MOID, std::vector<std::pair<Atom*, Vector>>> hitMOAtoms;
hitMOAtoms.clear();
thread_local std::deque<std::pair<Atom*, Vector>> hitTerrAtoms;
hitTerrAtoms.clear();
Expand Down
1 change: 1 addition & 0 deletions Source/Entities/SoundContainer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ SoundContainer::SoundContainer(const SoundContainer& reference) {
}

SoundContainer::~SoundContainer() {
g_AudioMan.DisownSoundContainerPlayingChannels(this);
Destroy(true);
}

Expand Down
6 changes: 4 additions & 2 deletions Source/Lua/LuaAdapters.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

#include "lj_obj.h"

#include <atomic>

using namespace RTE;

std::unordered_map<std::string, std::function<LuabindObjectWrapper*(Entity*, lua_State*)>> LuaAdaptersEntityCast::s_EntityToLuabindObjectCastFunctions = {};
Expand Down Expand Up @@ -293,8 +295,8 @@ void LuaAdaptersScene::CalculatePathAsync(Scene* luaSelfObject, const luabind::o
// As such, we need to store this function somewhere safely within our Lua state for us to access later when we need it
lua_State* luaState = mainthread(G(callback.interpreter())); // Get the main thread for the state, in case we're a temp lua thread

static int currentCallbackId = 0;
int thisCallbackId = currentCallbackId++;
static std::atomic<int> currentCallbackId{0};
int thisCallbackId = currentCallbackId.fetch_add(1, std::memory_order_relaxed);
if (luabind::type(callback) == LUA_TFUNCTION && callback.is_valid()) {
luabind::call_function<void>(luaState, "_AddAsyncPathCallback", thisCallbackId, callback);
}
Expand Down
23 changes: 20 additions & 3 deletions Source/Managers/AudioMan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,19 @@ bool AudioMan::StopSoundContainerPlayingChannels(SoundContainer* soundContainer,
return result == FMOD_OK;
}

void AudioMan::DisownSoundContainerPlayingChannels(const SoundContainer* soundContainer) {
if (!m_AudioEnabled || !soundContainer || !soundContainer->IsBeingPlayed()) {
return;
}
// Leave the channels playing, but null their back-reference so the positional/ended passes don't read the freed container.
FMOD::Channel* soundChannel;
for (int channelIndex: *soundContainer->GetPlayingChannels()) {
if (m_AudioSystem->getChannel(channelIndex, &soundChannel) == FMOD_OK) {
soundChannel->setUserData(nullptr);
}
}
}

void AudioMan::FadeOutSoundContainerPlayingChannels(SoundContainer* soundContainer, int fadeOutTime) {
if (!m_AudioEnabled || !soundContainer || !soundContainer->IsBeingPlayed()) {
return;
Expand Down Expand Up @@ -673,7 +686,7 @@ void AudioMan::Update3DEffectsForSFXChannels() {
float doubleMinimumDistanceForPanning = m_MinimumDistanceForPanning * 2.0F;
void* userData;
result = result == FMOD_OK ? soundChannel->getUserData(&userData) : result;
if (result == FMOD_OK) {
if (result == FMOD_OK && userData != nullptr) {
const SoundContainer* soundContainer = static_cast<SoundContainer*>(userData);
if (sqrDistanceToPlayer < (m_MinimumDistanceForPanning * m_MinimumDistanceForPanning) || soundContainer->GetCustomPanValue() != 0.0f) {
result = soundChannel->set3DLevel(0);
Expand Down Expand Up @@ -702,6 +715,9 @@ FMOD_RESULT AudioMan::UpdatePositionalEffectsForSoundChannel(FMOD::Channel* soun
}

const SoundContainer* channelSoundContainer = static_cast<SoundContainer*>(userData);
if (channelSoundContainer == nullptr) {
return FMOD_OK; // The owning SoundContainer was destroyed; leave the channel playing where it is.
}

bool sceneWraps = g_SceneMan.SceneWrapsX();

Expand Down Expand Up @@ -802,7 +818,7 @@ FMOD_RESULT F_CALLBACK AudioMan::SoundChannelEndedCallback(FMOD_CHANNELCONTROL*
result = (result == FMOD_OK) ? channel->getUserData(&userData) : result;
if (result == FMOD_OK) {
SoundContainer* channelSoundContainer = static_cast<SoundContainer*>(userData);
if (channelSoundContainer->IsBeingPlayed()) {
if (channelSoundContainer != nullptr && channelSoundContainer->IsBeingPlayed()) {
channelSoundContainer->RemovePlayingChannel(channelIndex);
}
result = (result == FMOD_OK) ? channel->setUserData(nullptr) : result;
Expand All @@ -812,7 +828,8 @@ FMOD_RESULT F_CALLBACK AudioMan::SoundChannelEndedCallback(FMOD_CHANNELCONTROL*
}

if (result != FMOD_OK) {
g_ConsoleMan.PrintString("ERROR: An error occurred when Ending a sound in SoundContainer " + channelSoundContainer->GetPresetName() + ": " + std::string(FMOD_ErrorString(result)));
const std::string containerName = channelSoundContainer != nullptr ? channelSoundContainer->GetPresetName() : "<destroyed>";
g_ConsoleMan.PrintString("ERROR: An error occurred when Ending a sound in SoundContainer " + containerName + ": " + std::string(FMOD_ErrorString(result)));
return result;
}
} else {
Expand Down
4 changes: 4 additions & 0 deletions Source/Managers/AudioMan.h
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,10 @@ namespace RTE {
/// @return
bool StopSoundContainerPlayingChannels(SoundContainer* soundContainer, int player);

/// Clears the back-reference from a destroyed SoundContainer's still-playing channels so they don't dangle.
/// @param soundContainer A pointer to the SoundContainer being destroyed. Ownership is NOT transferred!
void DisownSoundContainerPlayingChannels(const SoundContainer* soundContainer);

/// Fades out playback a SoundContainer.
/// @param soundContainer A pointer to a SoundContainer object. Ownership is NOT transferred!
/// @param fadeOutTime The amount of time, in ms, to fade out over.
Expand Down
2 changes: 2 additions & 0 deletions Source/Managers/MovableMan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ void MovableMan::Clear() {
m_MaxDroppedItems = 100;
m_SettlingEnabled = true;
m_MOSubtractionEnabled = true;
// HitWhatMOID / HitWhatTerrMaterial compare against this each tick; it's otherwise only incremented.
m_SimUpdateFrameNumber = 0;
}

int MovableMan::Initialize() {
Expand Down
6 changes: 6 additions & 0 deletions Source/Menus/SaveLoadMenuGUI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,13 @@ void SaveLoadMenuGUI::UpdateSaveGamesGUIList() {
const auto saveTime = std::chrono::system_clock::to_time_t(saveFsTime);
#else
// TODO - kill this monstrosity when we move to GCC13
// macOS libc++ file_clock rep is __int128; duration_cast lands it in system_clock's range first.
#if defined(__APPLE__) && defined(_LIBCPP_VERSION)
auto saveFsTime = std::chrono::system_clock::time_point(
std::chrono::duration_cast<std::chrono::system_clock::duration>(save.SaveDate.time_since_epoch()));
#else
auto saveFsTime = std::chrono::system_clock::time_point(save.SaveDate.time_since_epoch());
#endif
#ifdef _WIN32
// Windows epoch time are the number of seconds since... 1601-01-01 00:00:00. Seriously.
saveFsTime -= std::chrono::seconds(11644473600LL);
Expand Down
25 changes: 19 additions & 6 deletions Source/System/Atom.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,27 @@ void Atom::Clear() {
m_StepRatio = 1.0F;
m_SegProgress = 0.0F;

// SetupPos branches on m_IntPos before the first step sets it.
m_IntPos[X] = m_IntPos[Y] = 0;
m_PrevIntPos[X] = m_PrevIntPos[Y] = 0;

m_IgnoreMOIDsByGroup = 0;

// Note: These fields must be cleared to avoid a very edge case bug.
// While an AtomGroup is travelling, the OnCollideWithTerrain Lua function can run, which will in turn force Create to run if it hasn't already.
// If this Create function adds to an AtomGroup (e.g. adds an Attachable to it), there will be problems.
// Setting these values in Clear doesn't help if Atoms are removed at this point, but helps if Atoms are added, since these values mean the added Atoms won't try to step forwards.
// m_Dom = 0;
// m_Delta[m_Dom] = 0;
// Bresenham step state. A fresh Atom can be stepped before SetupSeg runs (an Attachable added
// mid-travel by an OnCollideWithTerrain script), so a stale pool value makes StepForward diverge.
m_TrailPos[X] = m_TrailPos[Y] = 0;
m_HitPos[X] = m_HitPos[Y] = 0;
m_Delta[X] = m_Delta[Y] = 0;
m_Delta2[X] = m_Delta2[Y] = 0;
m_Increment[X] = m_Increment[Y] = 0;
m_Error = 0;
m_Dom = 0;
m_Sub = 0;
m_DomSteps = 0;
m_SubSteps = 0;
m_SubStepped = false;
m_StepWasTaken = false;
m_SegTraj.Reset();
}

int Atom::Create(const Vector& offset, Material const* material, MovableObject* owner, Color trailColor, int trailLength) {
Expand Down
22 changes: 22 additions & 0 deletions Source/System/RTEError.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#endif

#include <array>
#include <cstdio>
#include <exception>
#include <regex>
#include <utility>
Expand All @@ -30,6 +31,14 @@
#include <filesystem>
#elif defined(__APPLE__) && defined(__MACH__)
#include <sys/sysctl.h>
#include <pthread.h>
#endif

#if defined(__APPLE__) && defined(__MACH__)
// Cocoa dispatches message boxes onto the main thread, which deadlocks if a worker fires one while the main thread waits on it.
static bool IsOnAppMainThread() { return pthread_main_np() != 0; }
#else
static bool IsOnAppMainThread() { return true; }
#endif

#include "backward/backward.hpp"
Expand Down Expand Up @@ -205,10 +214,18 @@ void RTEError::SetExceptionHandlers() {
}

void RTEError::ShowMessageBox(const std::string& message) {
if (!IsOnAppMainThread()) {
std::fprintf(stderr, "RTE Warning (from worker thread): %s\n", message.c_str());
return;
}
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_WARNING, "RTE Warning! (>_<)", message.c_str(), nullptr);
}

bool RTEError::ShowAbortMessageBox(const std::string& message) {
if (!IsOnAppMainThread()) {
std::fprintf(stderr, "RTE Abort (from worker thread): %s\n", message.c_str());
return false;
}
enum AbortMessageButton {
ButtonInvalid,
ButtonExit,
Expand Down Expand Up @@ -242,6 +259,11 @@ bool RTEError::ShowAbortMessageBox(const std::string& message) {
}

bool RTEError::ShowAssertMessageBox(const std::string& message) {
if (!IsOnAppMainThread()) {
// Return false (Ignore-once) so the worker can unwind; the main thread sees the assert on its next pass.
std::fprintf(stderr, "RTE Assert (from worker thread): %s\n", message.c_str());
return false;
}
enum AssertMessageButton {
ButtonInvalid,
ButtonAbort,
Expand Down
2 changes: 1 addition & 1 deletion external/sources/tracy/public/client/TracyProfiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1891,7 +1891,7 @@ void Profiler::Worker()

lastBroadcast = t;
const auto ts = std::chrono::duration_cast<std::chrono::seconds>( std::chrono::system_clock::now().time_since_epoch() ).count();
broadcastMsg.activeTime = int32_t( ts - m_epoch );
broadcastMsg.activeTime = ts > m_epoch ? int32_t( ts - m_epoch ) : 0;
assert( broadcastMsg.activeTime >= 0 );
m_broadcast->Send( broadcastPort, &broadcastMsg, broadcastLen );
}
Expand Down
Loading
Loading