Skip to content

Update readme #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 10, 2025
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -305,8 +305,8 @@ public class FeatureController2a : ControllerWithResultBase

protected override void OnStop()
{
base.OnStop();
_engineEvents.Win -= OnWin;
base.OnStop();
}

private void OnWin()
Expand Down Expand Up @@ -395,7 +395,7 @@ Alternatively, you can replace any controllers with mocks (for controllers with
```
```csharp
[Test]
public async Task LevelFlowController_ExecuteAndWaitResult_OOMEventAddMoves()
public async Task FeatureController2_ExecuteAndWaitResult_NoExceptionThrown()
{
var substituteControllerFactory = new SubstituteControllerFactory();
var myModelMock = Substitute.For<IMyModel>();
Expand Down
4 changes: 2 additions & 2 deletions README_ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,8 +287,8 @@ public class FeatureController2a : ControllerWithResultBase

protected override void OnStop()
{
base.OnStop();
_engineEvents.Win -= OnWin;
base.OnStop();
}

private void OnWin()
Expand Down Expand Up @@ -378,7 +378,7 @@ public async Task FeatureController2_ExecuteAndWaitResult_NoExceptionThrown()
```
```csharp
[Test]
public async Task LevelFlowController_ExecuteAndWaitResult_OOMEventAddMoves()
public async Task FeatureController2_ExecuteAndWaitResult_NoExceptionThrown()
{
var substituteControllerFactory = new SubstituteControllerFactory();
var myModelMock = Substitute.For<IMyModel>();
Expand Down
1 change: 1 addition & 0 deletions docs/AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ We thank the following people for their contributions (in alphabetical order):
- Aliaksandr Shchamialiou
- Andrei Batseka
- Andrei Navoichyk
- Andrew Chaiko
- Andrey Makarevich
- Artem Svirid
- Artur Sushkov
Expand Down
103 changes: 103 additions & 0 deletions src/ControllersTree/Core/Controllers/ControllerBase.Profiler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#if UNITY_CONTROLLERS_PROFILER
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Unity.Collections;
using UnityEngine.Profiling;

namespace Playtika.Controllers
{
public partial class ControllerBase
{
protected static int _totalCount;
protected static int _activeCount;
protected static int _createdThisFrameCount;

internal int _instanceId;
internal long _startTimeMSec;
private readonly Stopwatch _stopwatch = Stopwatch.StartNew();

internal List<IController> ChildControllers => _childControllers;
internal string ScopeName => _controllerFactory?.ToString();

internal long GetLifetime() => _stopwatch.ElapsedMilliseconds;
private HashSet<int> _savedNames = new HashSet<int>();

partial void ProfileOnCreated()
{
++_totalCount;
++_activeCount;
++_createdThisFrameCount;
}

partial void ProfileOnStart()
{
_startTimeMSec = _stopwatch.ElapsedMilliseconds;
PushCreateControllerToProfilerStream(Name, _startTimeMSec);
_stopwatch.Restart();
}

partial void ProfileOnStop()
{
--_activeCount;
var elapsedMilliseconds = _stopwatch.ElapsedMilliseconds;
PushStopControllerToProfilerStream(Name, elapsedMilliseconds);
}

private void PushCreateControllerToProfilerStream(
string name,
long startTimeMSec)
{
if (!Profiler.enabled || string.IsNullOrEmpty(name))
{
return;
}

var nameHash = name.GetHashCode();
var data = new NativeArray<CreateControllerFrameData>(1, Allocator.Persistent);
data[0] = new CreateControllerFrameData()
{
NameHash = nameHash,
StartTimeMSec = startTimeMSec
};

Profiler.EmitFrameMetaData(Helper.ControllerProfilerGuid, Helper.ControllerStartTag, data);
data.Dispose();

if (_savedNames.Contains(nameHash))
{
return;
}

var nameData = new NativeArray<NameSessionData>(1, Allocator.Persistent);
nameData[0] = new NameSessionData()
{
NameHash = nameHash,
Name = name
};
Profiler.EmitSessionMetaData(Helper.ControllerProfilerGuid, Helper.ControllerNameTag, nameData);
nameData.Dispose();
_savedNames.Add(nameHash);
}

private static void PushStopControllerToProfilerStream(string name, long elapsedMilliseconds)
{
if (!Profiler.enabled || string.IsNullOrEmpty(name))
{
return;
}

var data = new NativeArray<StopControllerFrameData>(1, Allocator.Persistent);
data[0] = new StopControllerFrameData()
{
NameHash = name.GetHashCode(),
ElapsedMilliseconds = elapsedMilliseconds
};

Profiler.EmitFrameMetaData(Helper.ControllerProfilerGuid, Helper.ControllerStopTag, data);
data.Dispose();
}
}
}
#endif

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion src/ControllersTree/Core/Controllers/ControllerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ public abstract partial class ControllerBase : IController, IDisposable

CancellationToken IController.CancellationToken => _lifetimeToken;

partial void ProfileOnCreated();
partial void ProfileOnStart();
partial void ProfileOnStop();

protected ControllerBase(IControllerFactory controllerFactory)
{
_controllerFactory = controllerFactory;
Expand Down Expand Up @@ -58,6 +62,7 @@ void IController.Initialize(
_lifetimeToken = _lifetimeTokenSource.Token;
_lifetimeToken.ThrowIfCancellationRequested();
_state = ControllerState.Initialized;
ProfileOnCreated();
break;
}
default:
Expand All @@ -72,8 +77,8 @@ void IController.Start()
case ControllerState.Initialized:
{
_state = ControllerState.Running;

OnStart();
ProfileOnStart();
break;
}
default:
Expand Down Expand Up @@ -101,6 +106,7 @@ void IController.Stop()
case ControllerState.Initialized:
case ControllerState.Running:
StopChildrenAndSelf();
ProfileOnStop();
break;
case ControllerState.Stopped:
case ControllerState.Disposed:
Expand Down
111 changes: 111 additions & 0 deletions src/ControllersTree/Core/Controllers/RootController.Profiler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#if UNITY_CONTROLLERS_PROFILER
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using Cysharp.Threading.Tasks;
using Unity.Collections;
using Unity.Profiling;
using UnityEngine.Profiling;

namespace Playtika.Controllers
{
public partial class RootController
{
private static readonly ProfilerCategory ControllerProfilerCategory = ProfilerCategory.Scripts;

private static readonly ProfilerCounterValue<int> TotalControllersCount =
new ProfilerCounterValue<int>(
ControllerProfilerCategory, Helper.TotalControllersCount,
ProfilerMarkerDataUnit.Count, ProfilerCounterOptions.FlushOnEndOfFrame);

private static readonly ProfilerCounterValue<int> ActiveControllersCount =
new ProfilerCounterValue<int>(
ControllerProfilerCategory, Helper.ActiveControllersCount,
ProfilerMarkerDataUnit.Count, ProfilerCounterOptions.FlushOnEndOfFrame);

private static readonly ProfilerCounterValue<int> CreateThisFrameControllersCount =
new ProfilerCounterValue<int>(
ControllerProfilerCategory, Helper.CreateThisFrameControllersCount,
ProfilerMarkerDataUnit.Count, ProfilerCounterOptions.FlushOnEndOfFrame);
private HashSet<int> _savedNames = new HashSet<int>();

partial void ProfileOnStart()
{
MadeSnapshot(CancellationToken).Forget();
}

private async UniTaskVoid MadeSnapshot(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
await UniTask.Yield(PlayerLoopTiming.LastUpdate);
PushToProfilerStream();
}
}

private void PushToProfilerStream()
{
if (!Profiler.enabled)
{
return;
}

TotalControllersCount.Value = _totalCount;
ActiveControllersCount.Value = _activeCount;
CreateThisFrameControllersCount.Value = _createdThisFrameCount;
_createdThisFrameCount = 0;

var count = _activeCount;
var frameData = new NativeArray<RunControllerFrameData>(count, Allocator.Persistent);
var frameDataIndex = 0;
ProfilerDumpTree((ControllerBase) this, frameData, ref frameDataIndex, 0);
Profiler.EmitFrameMetaData(Helper.ControllerProfilerGuid, Helper.ControllerRunTag, frameData);
frameData.Dispose();
}

private void ProfilerDumpTree(
ControllerBase controllerBase,
NativeArray<RunControllerFrameData> frameData,
ref int frameDataIndex,
int level)
{
var scopeName = controllerBase.ScopeName;
frameData[frameDataIndex] = new RunControllerFrameData()
{
NameHash = controllerBase.Name.GetHashCode(),
ScopeHash = scopeName.GetHashCode(),
InstanceId = controllerBase._instanceId,
Level = level,
StartTimeMSec = controllerBase._startTimeMSec,
ElapsedMilliseconds = controllerBase.GetLifetime(),
};

++frameDataIndex;

var nameHash = scopeName.GetHashCode();
if (!_savedNames.Contains(nameHash))
{
PushScopeNameToStream(scopeName, nameHash);
_savedNames.Add(nameHash);
}

foreach (var child in controllerBase.ChildControllers)
{
ProfilerDumpTree((ControllerBase) child, frameData, ref frameDataIndex, level + 2);
}
}

private static void PushScopeNameToStream(string name, int nameHash)
{
var nameData = new NativeArray<NameSessionData>(1, Allocator.Persistent);
nameData[0] = new NameSessionData()
{
NameHash = nameHash,
Name = name
};
Profiler.EmitSessionMetaData(Helper.ControllerProfilerGuid, Helper.ScopeNameTag, nameData);
nameData.Dispose();
}
}
}
#endif

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 7 additions & 3 deletions src/ControllersTree/Core/Controllers/RootController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@

namespace Playtika.Controllers
{
public abstract class RootController : ControllerBase
public abstract partial class RootController : ControllerBase
{
protected RootController(IControllerFactory controllerFactory) : base(controllerFactory)
protected RootController(IControllerFactory controllerFactory)
: base(controllerFactory)
{
}

partial void ProfileOnStart();

/// <summary>
/// Launches the execution of the controller tree.
/// This method initializes the root controller with the provided cancellation token, starts it, and registers a callback to stop the root controller when the cancellation token is cancelled.
Expand All @@ -31,6 +34,7 @@ private void StopRootController()
protected override void OnStart()
{
SetRootController(this);
ProfileOnStart();
}

protected override void OnStop()
Expand All @@ -55,4 +59,4 @@ private static void SetRootController(ControllerBase controller)
public static ControllerBase Instance { get; private set; }
#endif
}
}
}
5 changes: 3 additions & 2 deletions src/ControllersTree/Core/Playtika.Controllers.asmdef
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
"name": "Playtika.Controllers",
"rootNamespace": "",
"references": [
"UniTask"
"UniTask",
"Unity.Profiling.Core"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"allowUnsafeCode": true,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
Expand Down
8 changes: 8 additions & 0 deletions src/ControllersTree/Core/Profiler.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading