182 lines
4.4 KiB
C#
182 lines
4.4 KiB
C#
using System;
|
|
using System.Threading;
|
|
using KitsuneCafe.Extension;
|
|
using R3;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
using UnityEngine.Events;
|
|
using static KitsuneCafe.Extension.R3Extensions;
|
|
using TimeUnit = KitsuneCafe.Sys.TimeUnit;
|
|
|
|
namespace KitsuneCafe
|
|
{
|
|
public class Timer : MonoBehaviour
|
|
{
|
|
[SerializeField]
|
|
private float duration = 1;
|
|
public float Duration
|
|
{
|
|
get => duration;
|
|
set => duration = value;
|
|
}
|
|
|
|
[SerializeField]
|
|
private TimeUnit unit = TimeUnit.Seconds;
|
|
public TimeUnit Unit
|
|
{
|
|
get => unit;
|
|
set => unit = value;
|
|
}
|
|
|
|
[SerializeField]
|
|
private PlayerLoopTiming timing = PlayerLoopTiming.Update;
|
|
|
|
[SerializeField]
|
|
private TimeKind timeKind = TimeKind.Time;
|
|
|
|
[SerializeField]
|
|
private bool autostart = false;
|
|
|
|
[SerializeField]
|
|
private bool oneShot = false;
|
|
|
|
[SerializeField]
|
|
private UnityEvent onComplete = default;
|
|
|
|
private readonly ReactiveProperty<bool> started = new(false);
|
|
private readonly ReactiveProperty<bool> paused = new(false);
|
|
|
|
public bool Started => started.Value;
|
|
public bool Paused => paused.Value;
|
|
|
|
public IDisposable source;
|
|
|
|
public event EventHandler Completed;
|
|
|
|
private void Awake()
|
|
{
|
|
SubscribeToTimer();
|
|
}
|
|
|
|
private void SubscribeToTimer()
|
|
{
|
|
source?.Dispose();
|
|
|
|
var counting = Observable.Merge(
|
|
started.Compose(WhereTrue).Select(_ => true),
|
|
paused.Compose(WhereTrue).Select(_ => false)
|
|
);
|
|
|
|
var stopped = started.Where(KMath.Not).Skip(1);
|
|
|
|
var timeProvider = GetTimeProvider(timing, timeKind);
|
|
var totalDuration = GetDuration();
|
|
|
|
source = CreateTimer(GetDuration(), counting, timeProvider)
|
|
.TakeUntil(stopped)
|
|
// add onNext handler if remaining time is required
|
|
.Do(onCompleted: OnCompleted)
|
|
.Subscribe()
|
|
.AddTo(this);
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
if (autostart)
|
|
{
|
|
StartTimer();
|
|
}
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
if (Started && Paused)
|
|
{
|
|
Resume();
|
|
}
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
if (Started)
|
|
{
|
|
Pause();
|
|
}
|
|
}
|
|
|
|
public void StartTimer()
|
|
{
|
|
if (source == null)
|
|
{
|
|
SubscribeToTimer();
|
|
}
|
|
|
|
paused.Value = false;
|
|
started.Value = true;
|
|
}
|
|
|
|
public void StopTimer()
|
|
{
|
|
source?.Dispose();
|
|
source = null;
|
|
paused.Value = false;
|
|
started.Value = false;
|
|
}
|
|
|
|
public void Pause()
|
|
{
|
|
paused.Value = true;
|
|
}
|
|
|
|
public void Resume()
|
|
{
|
|
paused.Value = false;
|
|
}
|
|
|
|
private void OnCompleted(Result result)
|
|
{
|
|
StopTimer();
|
|
|
|
if (result.IsSuccess)
|
|
{
|
|
Finish();
|
|
|
|
if (!oneShot)
|
|
{
|
|
StartTimer();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void Finish()
|
|
{
|
|
Completed?.Invoke(this, EventArgs.Empty);
|
|
onComplete.Invoke();
|
|
}
|
|
|
|
private Observable<TimeSpan> CreateTimer(TimeSpan duration, Observable<bool> isCounting, TimeProvider timeProvider)
|
|
{
|
|
return Observable.Empty<Unit>()
|
|
.SwitchIf(isCounting, Observable.EveryUpdate(timeProvider.GetFrameProvider()))
|
|
.Select(_ => DeltaTime(timeProvider))
|
|
.Scan(duration, (acc, dt) => acc.Subtract(dt))
|
|
.TakeWhile(ts => ts > TimeSpan.Zero)
|
|
.DefaultIfEmpty(duration);
|
|
}
|
|
|
|
private Observable<Unit> WhereTrue(Observable<bool> source)
|
|
{
|
|
return source.WhereTrue().AsUnitObservable();
|
|
}
|
|
|
|
private TimeSpan GetDuration()
|
|
{
|
|
return Sys.Duration.From(duration, unit);
|
|
}
|
|
|
|
private TimeSpan DeltaTime(TimeProvider timeProvider)
|
|
{
|
|
return TimeSpan.FromSeconds(timeProvider.GetDeltaTime());
|
|
}
|
|
}
|
|
}
|