mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Implement PatrolReader
This commit is contained in:
parent
4ffc834427
commit
0964ecb2ee
6 changed files with 523 additions and 18 deletions
|
@ -25,7 +25,7 @@ public class DefaultFsServerObjects
|
|||
var sdCard = new EmulatedSdCard();
|
||||
|
||||
var gameCardNew = new GameCardDummy();
|
||||
var sdmmcNew = new SdmmcApi();
|
||||
var sdmmcNew = new SdmmcApi(fsServer);
|
||||
|
||||
var gcStorageCreator = new EmulatedGameCardStorageCreator(gameCard);
|
||||
|
||||
|
|
73
src/LibHac/Os/Event.cs
Normal file
73
src/LibHac/Os/Event.cs
Normal file
|
@ -0,0 +1,73 @@
|
|||
using System;
|
||||
|
||||
namespace LibHac.Os;
|
||||
|
||||
public struct EventType
|
||||
{
|
||||
}
|
||||
|
||||
public class Event : IDisposable
|
||||
{
|
||||
private EventType _event;
|
||||
|
||||
private readonly OsState _os;
|
||||
|
||||
public Event(OsState os, EventClearMode clearMode)
|
||||
{
|
||||
_os = os;
|
||||
_os.InitializeEvent(ref _event, signaled: false, clearMode);
|
||||
}
|
||||
|
||||
public void Dispose() => _os.FinalizeEvent(ref _event);
|
||||
public void Wait() => _os.WaitEvent(ref _event);
|
||||
public bool TryWait() => _os.TryWaitEvent(ref _event);
|
||||
public bool TimedWait(TimeSpan timeout) => _os.TimedWaitEvent(ref _event, timeout);
|
||||
public void Signal() => _os.SignalEvent(ref _event);
|
||||
public void Clear() => _os.ClearEvent(ref _event);
|
||||
public ref EventType GetBase() => ref _event;
|
||||
}
|
||||
|
||||
public static class EventApi
|
||||
{
|
||||
public static void InitializeEvent(this OsState os, ref EventType eventType, bool signaled,
|
||||
EventClearMode clearMode)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public static void FinalizeEvent(this OsState os, ref EventType eventType)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public static void SignalEvent(this OsState os, ref EventType eventType)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public static void WaitEvent(this OsState os, ref EventType eventType)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public static bool TryWaitEvent(this OsState os, ref EventType eventType)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public static bool TimedWaitEvent(this OsState os, ref EventType eventType, TimeSpan timeout)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public static void ClearEvent(this OsState os, ref EventType eventType)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public static void InitializeMultiWaitHolder(this OsState os, ref MultiWaitHolderType multiWaitHolder,
|
||||
ref EventType eventType)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
7
src/LibHac/Os/EventCommon.cs
Normal file
7
src/LibHac/Os/EventCommon.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace LibHac.Os;
|
||||
|
||||
public enum EventClearMode
|
||||
{
|
||||
ManualClear = 0,
|
||||
AutoClear = 1
|
||||
}
|
85
src/LibHac/Os/TimerEvent.cs
Normal file
85
src/LibHac/Os/TimerEvent.cs
Normal file
|
@ -0,0 +1,85 @@
|
|||
using System;
|
||||
|
||||
namespace LibHac.Os;
|
||||
|
||||
public struct TimerEventType
|
||||
{
|
||||
}
|
||||
|
||||
public class TimerEvent : IDisposable
|
||||
{
|
||||
private TimerEventType _event;
|
||||
|
||||
private readonly OsState _os;
|
||||
|
||||
public TimerEvent(OsState os, EventClearMode clearMode)
|
||||
{
|
||||
_os = os;
|
||||
_os.InitializeTimerEvent(ref _event, clearMode);
|
||||
}
|
||||
|
||||
public void Dispose() => _os.FinalizeTimerEvent(ref _event);
|
||||
public void StartOneShot(TimeSpan firstTime) => _os.StartOneShotTimerEvent(ref _event, firstTime);
|
||||
public void StartPeriodic(TimeSpan firstTime, TimeSpan interval) => _os.StartPeriodicTimerEvent(ref _event, firstTime, interval);
|
||||
public void Stop() => _os.StopTimerEvent(ref _event);
|
||||
public void Wait() => _os.WaitTimerEvent(ref _event);
|
||||
public bool TryWait() => _os.TryWaitTimerEvent(ref _event);
|
||||
public void Signal() => _os.SignalTimerEvent(ref _event);
|
||||
public void Clear() => _os.ClearTimerEvent(ref _event);
|
||||
public ref TimerEventType GetBase() => ref _event;
|
||||
}
|
||||
|
||||
public static class TimerEventApi
|
||||
{
|
||||
public static void InitializeTimerEvent(this OsState os, ref TimerEventType eventType, EventClearMode clearMode)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public static void FinalizeTimerEvent(this OsState os, ref TimerEventType eventType)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public static void StartOneShotTimerEvent(this OsState os, ref TimerEventType eventType, TimeSpan firstTime)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public static void StartPeriodicTimerEvent(this OsState os, ref TimerEventType eventType, TimeSpan firstTime,
|
||||
TimeSpan interval)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public static void StopTimerEvent(this OsState os, ref TimerEventType eventType)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public static void SignalTimerEvent(this OsState os, ref TimerEventType eventType)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public static void ClearTimerEvent(this OsState os, ref TimerEventType eventType)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public static void WaitTimerEvent(this OsState os, ref TimerEventType eventType)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public static bool TryWaitTimerEvent(this OsState os, ref TimerEventType eventType)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public static void InitializeMultiWaitHolder(this OsState os, ref MultiWaitHolderType multiWaitHolder,
|
||||
ref TimerEventType eventType)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using LibHac.FsSrv;
|
||||
|
||||
namespace LibHac.Sdmmc;
|
||||
|
||||
|
@ -60,6 +61,14 @@ public partial class SdmmcApi
|
|||
public const int DeviceCidSize = 0x10;
|
||||
public const int DeviceCsdSize = 0x10;
|
||||
|
||||
private FileSystemServer _fsServer;
|
||||
internal HorizonClient Hos => _fsServer.Hos;
|
||||
|
||||
public SdmmcApi(FileSystemServer fsServer)
|
||||
{
|
||||
_fsServer = fsServer;
|
||||
}
|
||||
|
||||
public void SwitchToPcvClockResetControl()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
|
|
|
@ -1,73 +1,404 @@
|
|||
using System;
|
||||
using LibHac.Common;
|
||||
using LibHac.Crypto;
|
||||
using LibHac.Diag;
|
||||
using LibHac.Fs;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Os;
|
||||
using LibHac.Sdmmc;
|
||||
using static LibHac.SdmmcSrv.Common;
|
||||
using MmcPartition = LibHac.Sdmmc.MmcPartition;
|
||||
|
||||
namespace LibHac.SdmmcSrv;
|
||||
|
||||
/// <summary>
|
||||
/// Performs regular patrol reads on the internal MMC.
|
||||
/// </summary>
|
||||
/// <remarks><para>The patrol reader will read sequential half-megabyte blocks of data from the internal MMC,
|
||||
/// starting at the beginning of the storage, progressing to the end, and then looping back to the beginning.
|
||||
/// This helps with data integrity by ensuring the entire MMC is regularly read.</para>
|
||||
/// <para>Every 6 seconds the patrol reader will read a half-megabyte block from the MMC.
|
||||
/// Every 2 hours it will save the current state of the patrol read to Boot Partition 1 on the MMC.
|
||||
/// This state contains the next sector index to be read and the number of times the MMC has been patrolled
|
||||
/// from start to finish.</para>
|
||||
/// <para>Based on nnSdk 14.3.0 (FS 14.1.0)</para></remarks>
|
||||
internal class PatrolReader
|
||||
{
|
||||
// Note: This class won't work until events and timer events are properly implemented.
|
||||
|
||||
private const int MacSize = HmacSha256.HashSize;
|
||||
|
||||
/// <summary>
|
||||
/// The patrol reader state that's stored in Boot Partition 1.
|
||||
/// </summary>
|
||||
public struct State
|
||||
{
|
||||
public uint CurrentSector;
|
||||
public uint PatrolCount;
|
||||
}
|
||||
|
||||
public enum ThreadState
|
||||
{
|
||||
Stop,
|
||||
Active,
|
||||
Sleep
|
||||
}
|
||||
|
||||
private readonly int _macSize;
|
||||
private readonly MmcPartition _statePartition;
|
||||
private readonly int _patrolStateOffset;
|
||||
private readonly int _patrolStateSize;
|
||||
private readonly int _saveStateIntervalSeconds;
|
||||
private readonly int _patrolReadSize;
|
||||
private readonly int _waitTimeAfterAllocationSuccess;
|
||||
private readonly int _waitTimeAfterAllocationFailure;
|
||||
private readonly int _firstRunDelaySeconds;
|
||||
private uint _deviceCapacitySectors;
|
||||
private State _patrolState;
|
||||
private bool _isPatrolStateLoaded;
|
||||
private SdkMutex _mutex;
|
||||
private bool _areEventsInitialized;
|
||||
private ThreadState _threadState;
|
||||
// private Thread _patrolReaderThread;
|
||||
private Event _stateChangeRequestedEvent;
|
||||
private Event _stateChangeCompletedEvent;
|
||||
private ulong _allocateSuccessCount;
|
||||
private ulong _allocateFailureCount;
|
||||
|
||||
// LibHac addition
|
||||
private SdmmcApi _sdmmc;
|
||||
|
||||
private static ReadOnlySpan<byte> PatrolStateKey => "U{W5>1Kq#Gt`f6r86o`9|*||hTy9U2C\0"u8;
|
||||
|
||||
public PatrolReader(SdkMutex mutex, SdmmcApi sdmmc)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
_macSize = MacSize;
|
||||
_statePartition = MmcPartition.BootPartition1;
|
||||
_patrolStateOffset = 0x184000;
|
||||
_patrolStateSize = 0x200;
|
||||
_saveStateIntervalSeconds = 7200;
|
||||
_patrolReadSize = 0x80000;
|
||||
_waitTimeAfterAllocationSuccess = 6;
|
||||
_waitTimeAfterAllocationFailure = 1;
|
||||
_firstRunDelaySeconds = 18;
|
||||
_isPatrolStateLoaded = false;
|
||||
_mutex = mutex;
|
||||
_areEventsInitialized = false;
|
||||
_threadState = ThreadState.Stop;
|
||||
_allocateSuccessCount = 0;
|
||||
_allocateFailureCount = 0;
|
||||
|
||||
_sdmmc = sdmmc;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
FinalizeObject();
|
||||
}
|
||||
|
||||
public static void PatrolReaderThreadEntry(object args)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (args is PatrolReader reader)
|
||||
{
|
||||
reader.PatrolReaderThread();
|
||||
}
|
||||
else
|
||||
{
|
||||
Abort.DoAbort($"Expected an argument of type {nameof(PatrolReader)} in {nameof(PatrolReaderThreadEntry)}");
|
||||
}
|
||||
}
|
||||
|
||||
public void FinalizeObject()
|
||||
private void FinalizeObject()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (_areEventsInitialized)
|
||||
{
|
||||
if (_threadState == ThreadState.Sleep)
|
||||
{
|
||||
Resume();
|
||||
}
|
||||
|
||||
if (_threadState == ThreadState.Active)
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
_stateChangeRequestedEvent.Dispose();
|
||||
_stateChangeCompletedEvent.Dispose();
|
||||
|
||||
_areEventsInitialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
public Result GetPatrolCount(out uint outCount)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
UnsafeHelpers.SkipParamInit(out outCount);
|
||||
|
||||
if (!_isPatrolStateLoaded)
|
||||
return ResultFs.HasNotGottenPatrolCount.Log();
|
||||
|
||||
outCount = _patrolState.PatrolCount;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public void GetAndClearAllocateCount(out ulong outSuccessCount, out ulong outFailureCount)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
using ScopedLock<SdkMutex> scopedLock = ScopedLock.Lock(ref _mutex);
|
||||
|
||||
outSuccessCount = _allocateSuccessCount;
|
||||
outFailureCount = _allocateFailureCount;
|
||||
|
||||
_allocateSuccessCount = 0;
|
||||
_allocateFailureCount = 0;
|
||||
}
|
||||
|
||||
public bool LoadState()
|
||||
private bool LoadState()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
using var pooledBuffer = new PooledBuffer(_patrolStateSize, 1);
|
||||
if (pooledBuffer.GetSize() < _patrolStateSize)
|
||||
return false;
|
||||
|
||||
using ScopedLock<SdkMutex> scopedLock = ScopedLock.Lock(ref _mutex);
|
||||
|
||||
Abort.DoAbortUnlessSuccess(_sdmmc.SelectMmcPartition(Port.Mmc0, _statePartition));
|
||||
|
||||
try
|
||||
{
|
||||
Result res = _sdmmc.GetDeviceMemoryCapacity(out _deviceCapacitySectors, Port.Mmc0);
|
||||
if (res.IsFailure())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool SaveState()
|
||||
Span<byte> patrolStateBuffer = pooledBuffer.GetBuffer().Slice(0, _patrolStateSize);
|
||||
|
||||
res = _sdmmc.Read(patrolStateBuffer, Port.Mmc0, BytesToSectors(_patrolStateOffset),
|
||||
BytesToSectors(_patrolStateSize));
|
||||
|
||||
if (res.IsFailure())
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return false;
|
||||
}
|
||||
|
||||
Span<byte> mac = stackalloc byte[MacSize];
|
||||
|
||||
// Load an empty state if the verification fails.
|
||||
_patrolState = default;
|
||||
|
||||
HmacSha256.GenerateHmacSha256(mac, patrolStateBuffer.Slice(_macSize), PatrolStateKey);
|
||||
|
||||
if (CryptoUtil.IsSameBytes(mac, patrolStateBuffer, _macSize))
|
||||
{
|
||||
ref State readState = ref SpanHelpers.AsStruct<State>(patrolStateBuffer.Slice(_macSize));
|
||||
|
||||
if (readState.CurrentSector + BytesToSectors(_patrolReadSize) <= _deviceCapacitySectors)
|
||||
{
|
||||
_patrolState = readState;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Abort.DoAbortUnlessSuccess(_sdmmc.SelectMmcPartition(Port.Mmc0, MmcPartition.UserData));
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveState()
|
||||
{
|
||||
using var pooledBuffer = new PooledBuffer(_patrolStateSize, 1);
|
||||
|
||||
if (pooledBuffer.GetSize() < _patrolStateSize)
|
||||
return;
|
||||
|
||||
using ScopedLock<SdkMutex> scopedLock = ScopedLock.Lock(ref _mutex);
|
||||
|
||||
Span<byte> patrolStateBuffer = pooledBuffer.GetBuffer().Slice(0, _patrolStateSize);
|
||||
patrolStateBuffer.Clear();
|
||||
|
||||
SpanHelpers.AsStruct<State>(patrolStateBuffer) = _patrolState;
|
||||
|
||||
HmacSha256.GenerateHmacSha256(patrolStateBuffer.Slice(0, _macSize), patrolStateBuffer.Slice(_macSize),
|
||||
PatrolStateKey);
|
||||
|
||||
Abort.DoAbortUnlessSuccess(_sdmmc.SelectMmcPartition(Port.Mmc0, _statePartition));
|
||||
|
||||
try
|
||||
{
|
||||
_sdmmc.Write(Port.Mmc0, BytesToSectors(_patrolStateOffset), BytesToSectors(_patrolStateSize),
|
||||
patrolStateBuffer);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Abort.DoAbortUnlessSuccess(_sdmmc.SelectMmcPartition(Port.Mmc0, MmcPartition.UserData));
|
||||
}
|
||||
}
|
||||
|
||||
private bool DoPatrolRead()
|
||||
{
|
||||
using var pooledBuffer = new PooledBuffer(_patrolReadSize, 1);
|
||||
using ScopedLock<SdkMutex> scopedLock = ScopedLock.Lock(ref _mutex);
|
||||
|
||||
if (pooledBuffer.GetSize() < _patrolReadSize)
|
||||
{
|
||||
if (_allocateFailureCount != ulong.MaxValue)
|
||||
_allocateFailureCount++;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_allocateSuccessCount != ulong.MaxValue)
|
||||
_allocateSuccessCount++;
|
||||
|
||||
_sdmmc.Read(pooledBuffer.GetBuffer().Slice(0, _patrolReadSize), Port.Mmc0, _patrolState.CurrentSector,
|
||||
BytesToSectors(_patrolReadSize)).IgnoreResult();
|
||||
|
||||
_patrolState.CurrentSector += BytesToSectors(_patrolReadSize);
|
||||
|
||||
if (_patrolState.CurrentSector + BytesToSectors(_patrolReadSize) > _deviceCapacitySectors)
|
||||
{
|
||||
_patrolState.CurrentSector = 0;
|
||||
_patrolState.PatrolCount++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
Assert.SdkAssert(_threadState is ThreadState.Active or ThreadState.Stop);
|
||||
|
||||
if (_threadState == ThreadState.Stop)
|
||||
{
|
||||
if (!_areEventsInitialized)
|
||||
{
|
||||
_areEventsInitialized = true;
|
||||
_threadState = ThreadState.Stop;
|
||||
|
||||
_stateChangeRequestedEvent = new Event(_sdmmc.Hos.Os, EventClearMode.AutoClear);
|
||||
_stateChangeCompletedEvent = new Event(_sdmmc.Hos.Os, EventClearMode.AutoClear);
|
||||
}
|
||||
|
||||
_threadState = ThreadState.Active;
|
||||
|
||||
// CreateThread(_patrolReaderThread, PatrolReaderThreadEntry, this, pPatrolReaderStack, priority);
|
||||
// SetThreadNamePointer(_patrolReaderThread, "nn.fs.PatrolReader"u8);
|
||||
// StartThread(_patrolReaderThread);
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
Assert.SdkAssert(_threadState is ThreadState.Active or ThreadState.Stop or ThreadState.Sleep);
|
||||
|
||||
if (_threadState != ThreadState.Stop)
|
||||
{
|
||||
_threadState = ThreadState.Stop;
|
||||
_stateChangeRequestedEvent.Signal();
|
||||
|
||||
// WaitThread(_patrolReaderThread);
|
||||
// DestroyThread(_patrolReaderThread);
|
||||
}
|
||||
}
|
||||
|
||||
public void Sleep()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
Assert.SdkAssert(_threadState is ThreadState.Active or ThreadState.Sleep);
|
||||
|
||||
if (_threadState == ThreadState.Active)
|
||||
{
|
||||
_threadState = ThreadState.Sleep;
|
||||
|
||||
_stateChangeRequestedEvent.Signal();
|
||||
_stateChangeCompletedEvent.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
public void Resume()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
Assert.SdkAssert(_threadState is ThreadState.Active or ThreadState.Sleep);
|
||||
|
||||
if (_threadState == ThreadState.Sleep)
|
||||
{
|
||||
_threadState = ThreadState.Active;
|
||||
|
||||
_stateChangeRequestedEvent.Signal();
|
||||
_stateChangeCompletedEvent.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
public void PatrolReaderThread()
|
||||
private void PatrolReaderThread()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// Missing: SetServiceContext()
|
||||
|
||||
using var timer = new TimerEvent(_sdmmc.Hos.Os, EventClearMode.AutoClear);
|
||||
|
||||
timer.StartPeriodic(TimeSpan.FromSeconds(_firstRunDelaySeconds), TimeSpan.FromSeconds(_saveStateIntervalSeconds));
|
||||
|
||||
int currentWaitTime = _waitTimeAfterAllocationSuccess;
|
||||
|
||||
while (true)
|
||||
{
|
||||
// Wait until the next thread state change or until the next patrol read should be done.
|
||||
if (_stateChangeRequestedEvent.TimedWait(TimeSpan.FromSeconds(currentWaitTime)))
|
||||
{
|
||||
if (_threadState == ThreadState.Stop)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (_threadState == ThreadState.Sleep)
|
||||
{
|
||||
// Acknowledge the request to sleep and wait for the next state change.
|
||||
_stateChangeCompletedEvent.Signal();
|
||||
_stateChangeRequestedEvent.Wait();
|
||||
|
||||
if (_threadState == ThreadState.Stop)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (_threadState == ThreadState.Active)
|
||||
{
|
||||
_stateChangeCompletedEvent.Signal();
|
||||
continue;
|
||||
}
|
||||
|
||||
Assert.SdkAssert(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (!_isPatrolStateLoaded)
|
||||
{
|
||||
// The patrol state will be loaded a single time when the console is booted.
|
||||
// Don't load the patrol state or do patrol reads until the specified amount
|
||||
// of time has past since creating the Patrol Reader.
|
||||
if (!timer.TryWait() || !LoadState())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_isPatrolStateLoaded = true;
|
||||
}
|
||||
|
||||
if (DoPatrolRead())
|
||||
{
|
||||
currentWaitTime = _waitTimeAfterAllocationSuccess;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentWaitTime = _waitTimeAfterAllocationFailure;
|
||||
}
|
||||
|
||||
// Save the patrol state periodically once the specified amount of time has passed since the last save.
|
||||
if (timer.TryWait())
|
||||
{
|
||||
SaveState();
|
||||
}
|
||||
}
|
||||
|
||||
// Save the patrol state if necessary when shutting down.
|
||||
if (_isPatrolStateLoaded)
|
||||
SaveState();
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue