Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
cf8031c
Update tech debt report [skip ci]
github-actions[bot] Mar 23, 2026
86c5392
Merge pull request #3 from sir306/dev
sir306 Mar 23, 2026
2954b19
Update tech debt report [skip ci]
github-actions[bot] Mar 23, 2026
57bfcf1
Update activity report [skip ci]
github-actions[bot] Mar 23, 2026
5cc2c21
Update tech debt report [skip ci]
github-actions[bot] Mar 23, 2026
9fc4d79
Merge pull request #4 from sir306/dev
sir306 Mar 24, 2026
dddd5cc
Merge pull request #5 from sir306/dev
sir306 Mar 24, 2026
abc1e82
Improve macOS screenshot handling and packaging docs
sir306 Mar 30, 2026
d513d1e
Clarify Unreal build steps and macOS Xcode notes
sir306 Mar 31, 2026
a1189ba
Docs: clarify macOS build & Xcode support
sir306 Mar 31, 2026
741fa8b
Merge pull request #6 from sir306/dev-mac-update-documentation
sir306 Apr 7, 2026
688ff40
Reduce HDF5 memory retention and harden Mass template teardown
sir306 Apr 9, 2026
7b14ee7
Add memory tracing, tests and unify entity data
sir306 Apr 10, 2026
e509902
Finish SimulationData TSharedPtr move and harden FlowCounter nulls
sir306 Apr 14, 2026
446a4a3
Add memory tracing and async-safety fixes
sir306 Apr 15, 2026
361388d
Fix UObject leaks during Datasmith and simulation file switches
sir306 Apr 16, 2026
14d51ed
Free Datasmith scene resources on EndPlay
sir306 Apr 17, 2026
5b8b737
Unbind GameInstance delegate; update uasset LFS
sir306 Apr 19, 2026
dc099ca
Cache GameInstance to manage delegate binding
sir306 Apr 19, 2026
b182b12
Update DT_MobiusLogToggleSettings.uasset
sir306 Apr 19, 2026
b304aeb
Improve thread joining and subsystem safety
sir306 Apr 20, 2026
95d5538
Splash image Visual tweaks and resize
sir306 Apr 20, 2026
3fc20c0
Staggered mesh/tile section emits and batching
sir306 Apr 21, 2026
c81222b
Improve flow-counter spawn strategy and diagnostics
sir306 Apr 24, 2026
30a78f2
Update AgentDataSubsystem.cpp
sir306 Apr 24, 2026
3cfe929
Decouple widget subsystem and tidy module deps
sir306 Apr 24, 2026
352ab78
Replace EditorAssetLibrary & remove dependency
sir306 Apr 24, 2026
4796441
Merge pull request #8 from sir306/hdf5-memory-cleanup
sir306 Apr 27, 2026
7a38948
Ignore default branch pushes in todo workflow
sir306 Apr 27, 2026
041d764
Merge branch 'main' of https://github.com/sir306/ProjectMobius
sir306 Apr 27, 2026
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
4 changes: 3 additions & 1 deletion .github/workflows/todo-tracker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ on:
- cron: '30 6 * * 1'
workflow_dispatch:
push:
branches: [main]
branches-ignore:
- main
paths:
- 'UnrealFolder/ProjectMobius/Source/**/*.h'
- 'UnrealFolder/ProjectMobius/Source/**/*.cpp'
Expand All @@ -18,6 +19,7 @@ jobs:
track-todos:
name: Scan TODOs
runs-on: ubuntu-latest
if: github.ref_name != github.event.repository.default_branch
permissions:
contents: write

Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ UnrealFolder/ProjectMobius/Plugins/Hdf5DataPlugin/Source/ThirdParty/hdf5-2.0.0/i
UnrealFolder/ProjectMobius/Plugins/Hdf5DataPlugin/Source/ThirdParty/hdf5-2.0.0/install/lib/pkgconfig/
UnrealFolder/ProjectMobius/Plugins/Hdf5DataPlugin/Source/ThirdParty/hdf5-2.0.0/install/lib/libhdf5.settings
UnrealFolder/ProjectMobius/Plugins/Hdf5DataPlugin/Source/ThirdParty/hdf5-2.0.0/install/include/H5pubconf.h
UnrealFolder/ProjectMobius/Plugins/Hdf5DataPlugin/Source/ThirdParty/hdf5-2.0.0/install/share/

# HDF5 third-party (scoped to vendored path)
UnrealFolder/ProjectMobius/Plugins/Hdf5DataPlugin/Source/ThirdParty/hdf5-2.0.0/**/java/.classes
Expand All @@ -190,6 +191,7 @@ UnrealFolder/ProjectMobius/Plugins/Hdf5DataPlugin/Source/ThirdParty/hdf5-2.0.0/.
UnrealFolder/ProjectMobius/Plugins/Hdf5DataPlugin/Source/ThirdParty/hdf5-2.0.0/CLAUDE.md
UnrealFolder/ProjectMobius/Plugins/Hdf5DataPlugin/Source/ThirdParty/hdf5-2.0.0/CMakeUserPresets.json
UnrealFolder/ProjectMobius/Plugins/Hdf5DataPlugin/Source/ThirdParty/hdf5-2.0.0/HDF5Examples/CMakeUserPresets.json
UnrealFolder/ProjectMobius/UnitTestSampleData/**/MobiusCaptures/


# =====================================================
Expand Down
Binary file added ImportedOpenSourceAssets/MobiusIconResize.ico
Binary file not shown.
3 changes: 3 additions & 0 deletions ImportedOpenSourceAssets/MobiusSplashResize.bmp
Git LFS file not shown
3 changes: 3 additions & 0 deletions ImportedOpenSourceAssets/MobiusSplashResize.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion UnrealFolder/ProjectMobius/Config/DefaultEngine.ini
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ r.TemporalAA.Upsampling=False
r.DefaultFeature.Bloom=False
r.DefaultBackBufferPixelFormat=4
r.AllowStaticLighting=False
r.MegaLights.EnableForProject=True
r.RayTracing.Shadows=True
r.VertexFoggingForOpaque=False
r.Velocity.EnableVertexDeformation=1
Expand Down Expand Up @@ -374,3 +373,5 @@ AutoRepair=False
bAutoRepair=False
EnabledByDefault=False

[/Script/MacTargetPlatform.XcodeProjectSettings]
bMacSignToRunLocally=True
5 changes: 3 additions & 2 deletions UnrealFolder/ProjectMobius/Config/DefaultGame.ini
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ ResolutionSizeY=720
FullscreenMode=2
LicensingTerms=/** * MIT License * Copyright (c) 2025 ProjectMobius contributors * Nicholas R. Harding and Peter Thompson * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is furnished * to do so, subject to the following conditions: * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */
bAllowWindowResize=True
ProjectDisplayedTitle=NSLOCTEXT("[/Script/EngineSettings]", "488452E64D1E28DD7244EA8BD87B7820", "Mobius Viewer")

[StartupActions]
bAddPacks=True
Expand All @@ -20,7 +21,7 @@ Build=IfProjectHasCode
BuildConfiguration=PPBC_DebugGame
BuildTarget=ProjectMobius
FullRebuild=True
ForDistribution=True
ForDistribution=False
IncludeDebugFiles=True
BlueprintNativizationMethod=Disabled
bIncludeNativizedAssetsInProjectGeneration=False
Expand Down Expand Up @@ -57,7 +58,7 @@ bShareMaterialShaderCode=True
bDeterministicShaderCodeOrder=False
bSharedMaterialNativeLibraries=True
ApplocalPrerequisitesDirectory=(Path="")
IncludeCrashReporter=False
IncludeCrashReporter=True
InternationalizationPreset=English
-CulturesToStage=en
+CulturesToStage=en
Expand Down
1 change: 1 addition & 0 deletions UnrealFolder/ProjectMobius/Config/DefaultInput.ini
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ DefaultTouchInterface=/Engine/MobileResources/HUD/DefaultVirtualJoysticks.Defaul
-ConsoleKeys=Tilde
+ConsoleKeys=Tilde
+ConsoleKeys=Caret
+ConsoleKeys=RightBracket

[/Script/EnhancedInput.EnhancedInputDeveloperSettings]
+DefaultMappingContexts=(InputMappingContext="/Game/00_UserAndInputs/InputMappings/VR_Inputs/IMC_Default.IMC_Default",Priority=0,bAddImmediately=True,bRegisterWithUserSettings=False)
Expand Down
2 changes: 2 additions & 0 deletions UnrealFolder/ProjectMobius/Config/Mac/MacEngine.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[/Script/Engine.RendererSettings]
r.MegaLights.EnableForProject=False
2 changes: 2 additions & 0 deletions UnrealFolder/ProjectMobius/Config/Windows/WindowsEngine.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[/Script/Engine.RendererSettings]
r.MegaLights.EnableForProject=True
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
4 changes: 2 additions & 2 deletions UnrealFolder/ProjectMobius/Content/Splash/EdSplash.bmp
Git LFS file not shown
3 changes: 0 additions & 3 deletions UnrealFolder/ProjectMobius/Content/Splash/EdSplash.uasset

This file was deleted.

This file was deleted.

4 changes: 2 additions & 2 deletions UnrealFolder/ProjectMobius/Content/Splash/Splash.bmp
Git LFS file not shown
3 changes: 3 additions & 0 deletions UnrealFolder/ProjectMobius/Content/Splash/Splash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 0 additions & 3 deletions UnrealFolder/ProjectMobius/Content/Splash/Splash.uasset

This file was deleted.

5 changes: 5 additions & 0 deletions UnrealFolder/ProjectMobius/ProjectMobius.uproject
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@
"Name": "MobiusEditor",
"Type": "Editor",
"LoadingPhase": "Default"
},
{
"Name": "ProjectMobiusTests",
"Type": "Editor",
"LoadingPhase": "Default"
}
],
"Plugins": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
#include "Components/DeformableQuadComponent.h"
#include "Kismet/GameplayStatics.h"
#include "Subsystems/StatisticActorManagementSubsystem.h"
#include "Subsystems/StatisticSubsystem.h"
#include "Subsystems/TimeDilationSubSystem.h"
#include "Subsystems/MobiusUserFeedbackSubsystem.h"
#include "Util/MemoryTraceHelper.h"

// Shared base material for all FlowCounters (loaded once, reused).
static TWeakObjectPtr<UMaterialInterface> GFlowCounterBaseMaterial;
Expand Down Expand Up @@ -442,6 +442,12 @@ void AFlowCounter::BeginPlay()

void AFlowCounter::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
#if !UE_BUILD_SHIPPING
FMobiusMemSnapshot SnapFcEndStart = FMobiusMemSnapshot::Take(
FString::Printf(TEXT("FC_EndPlay_Start[%s:%d/%d]"),
*GetName(), AgentsPassedThroughCounter.Num(), PreviousTrackedAgentLocations.Num()));
#endif

bTearingDown.Store(true); // visible to all threads

// 1) Unregister from subsystems so no NEW calls are scheduled
Expand All @@ -453,6 +459,9 @@ void AFlowCounter::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Time->OnNewCurrentTime.RemoveDynamic(this, &AFlowCounter::NewSimTime);
}

// Cancel pending colour-reset timer so it can't fire on a destroyed actor
World->GetTimerManager().ClearTimer(FlowColorResetHandle);
}

// 2) Drain any internal queues so Tick (or anyone) won’t commit after teardown
Expand All @@ -468,6 +477,15 @@ void AFlowCounter::EndPlay(const EEndPlayReason::Type EndPlayReason)
PreviousTrackedAgentLocations.Empty();
}

// Drop the dynamic material instance reference. UE actor destruction reclaims
// declared subobjects but not MIDs created via CreateDynamicMaterialInstance,
// which otherwise remain rooted via this UPROPERTY across simulation switches.
CounterBarrierVisualMID = nullptr;

#if !UE_BUILD_SHIPPING
FMobiusMemSnapshot::Take(FString::Printf(TEXT("FC_EndPlay_End[%s]"), *GetName())).LogDelta(SnapFcEndStart);
#endif

Super::EndPlay(EndPlayReason);
}

Expand All @@ -476,13 +494,6 @@ void AFlowCounter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);

// FBuckectTempData BucketData;
// // Dequeue any bucket data
// while (ThreadSafeNewAgentDataQueue.Dequeue(BucketData))
// {
// AssignAgentToBucketUsingThreshold(BucketData.AgentID, BucketData.IntersectionThreshold);
// }

if (bIsFlowCounterActive)
{
FFlowCrossingResult R;
Expand Down Expand Up @@ -610,9 +621,11 @@ void AFlowCounter::UpdateFlowCounterTriggerBoxLocation(const FVector& NewLocatio

void AFlowCounter::UpdateFlowCounterTriggerBox()
{
if (FlowCounterTriggerBox == nullptr)
if (FlowCounterTriggerBox == nullptr ||
FlowCounterPillarMesh1 == nullptr ||
FlowCounterPillarMesh2 == nullptr)
{
return;// Early exit if the trigger box is not valid
return;// Early exit if any required component is missing
}
// Update the FlowCounterLineStartLocation and FlowCounterLineEndLocation based on the pillar locations
FlowCounterLineStartLocation = FlowCounterPillarMesh1->GetComponentLocation();
Expand Down Expand Up @@ -687,9 +700,9 @@ void AFlowCounter::UpdateFlowCounterTriggerBox()
bool AFlowCounter::ProcessAgentFlowCrossing(const FFlowCounterData& Data)
{
if (bTearingDown.Load()) return false;
TWeakObjectPtr<AFlowCounter> WeakThis(this);
AFlowCounter* FlowCounter = WeakThis.Get();
if (FlowCounter == nullptr) { return false; }

TWeakObjectPtr<AFlowCounter> WeakThis(this); // captured by AsyncTask lambdas further down
AFlowCounter* FlowCounter = this;

if (!FlowCounter->bIsFlowCounterActive)
{
Expand All @@ -701,7 +714,12 @@ bool AFlowCounter::ProcessAgentFlowCrossing(const FFlowCounterData& Data)

// We need to store the current sim time when we start processing agents - as this could change while processing
float ProcessTime = FlowCounter->CurrentSimTime;


if (FlowCounter->FlowCounterTriggerBox == nullptr)
{
return false;
}

// Get the flow counter trigger box so we can check if the agent is within the box
UE::Math::TBox FlowCounterBox = FlowCounter->FlowCounterTriggerBox->Bounds.GetBox();

Expand Down Expand Up @@ -930,6 +948,23 @@ void AFlowCounter::RemoveFlowCounterToSubsystem()

void AFlowCounter::ResetFlowCounterTrackingData()
{
#if !UE_BUILD_SHIPPING
// Skip the probe log when there's nothing to reset. At spawn this method fires
// four times per counter (from MoveGatePillar → UpdateFlowCounterTriggerBox
// path x2 pillars) with empty maps, spamming the log. Only probe when real
// data is being cleared.
const bool bHadData = AgentsPassedThroughCounter.Num() > 0
|| PreviousTrackedAgentLocations.Num() > 0
|| FlowCounterCount.load() > 0;
TOptional<FMobiusMemSnapshot> SnapResetStart;
if (bHadData)
{
SnapResetStart = FMobiusMemSnapshot::Take(
FString::Printf(TEXT("FC_Reset_Start[%s:passed=%d,tracked=%d,buckets=%d]"),
*GetName(), AgentsPassedThroughCounter.Num(), PreviousTrackedAgentLocations.Num(), FlowCounterBucketData.Num()));
}
#endif

// Reset the flow counter count to 0
FlowCounterCount.exchange(0);
// Clear the previous tracked agent locations
Expand All @@ -951,6 +986,13 @@ void AFlowCounter::ResetFlowCounterTrackingData()
Bucket.AgentIDs.Empty();
Bucket.AgentCount = 0;
}

#if !UE_BUILD_SHIPPING
if (SnapResetStart.IsSet())
{
FMobiusMemSnapshot::Take(FString::Printf(TEXT("FC_Reset_End[%s]"), *GetName())).LogDelta(SnapResetStart.GetValue());
}
#endif
}

void AFlowCounter::NewSimTime(float UpdatedTime)
Expand Down Expand Up @@ -1047,11 +1089,13 @@ void AFlowCounter::FlashBarrierColor()
// schedule revert to BLUE after 0.3s (restart if already running)
FTimerManager& TM = GetWorldTimerManager();
TM.ClearTimer(FlowColorResetHandle);
TM.SetTimer(FlowColorResetHandle, [this]()
TWeakObjectPtr<AFlowCounter> WeakSelf(this);
TM.SetTimer(FlowColorResetHandle, [WeakSelf]()
{
if (IsValid(CounterBarrierVisualMID))
AFlowCounter* Self = WeakSelf.Get();
if (Self && IsValid(Self->CounterBarrierVisualMID))
{
CounterBarrierVisualMID->SetVectorParameterValue(FlowColorParam, FLinearColor::Blue);
Self->CounterBarrierVisualMID->SetVectorParameterValue(Self->FlowColorParam, FLinearColor::Blue);
}
}, 0.3f, false);
}
Expand All @@ -1066,7 +1110,9 @@ void AFlowCounter::AssignAgentsToBuckets(TArray<int32> AllAgents)

void AFlowCounter::AssignAgentToBuckets(int32 AgentID)
{
// Get the agent's intersection location from the AgentsPassedThroughCounter map
// GT-only: reads AgentsPassedThroughCounter without AgentsMapRW lock. All current
// callers run on the game thread (Tick, NewSimTime, BlueprintCallable). Add locking
// if this ever grows a non-GT caller.
FFlowCounterCountedAgentData* AgentData = AgentsPassedThroughCounter.Find(AgentID);

// is the data ptr valid ?
Expand All @@ -1077,9 +1123,7 @@ void AFlowCounter::AssignAgentToBuckets(int32 AgentID)
{
// we need to check if the agent's intersection location is within the bucket segment or on the start/end point of the segment
// TODO: Review this logic with Pete to ensure this is how we should be checking

FFlowCounterBucketData BucketData = FlowCounterBucketData[i];


// if (IsPointOnLineSegment(BucketData.SegmentStart, BucketData.SegmentEnd, AgentData->IntersectionLocation))
// {
// // Add the agent to the bucket
Expand Down Expand Up @@ -1154,7 +1198,8 @@ void AFlowCounter::RemoveAgentFromBuckets(int32 AgentID)

void AFlowCounter::UpdateFlowBucketsWithCurrentAgentsFromTimeChange()
{
// Get all the Agent IDs from the AgentsPassedThroughCounter map
// GT-only: reads AgentsPassedThroughCounter without AgentsMapRW lock. Called from
// NewSimTime on the game thread; if this ever grows a non-GT caller, add locking.
TArray<int32> AllAgents;
AgentsPassedThroughCounter.GetKeys(AllAgents);

Expand Down
Loading
Loading