Skip to content
Merged
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
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.
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
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