스케줄러

원샷 지연, 반복 타이머, 다음 프레임 콜백, 그리고 백그라운드 스레드에서 메인 스레드로 건너오는 안전한 다리.

코드를 나중에 실행하세요 — 지연 후에, 일정 간격마다, 다음 프레임에, 또는 메인 스레드로 돌아와서.

인터페이스IScheduler
끄기 스위치NullScheduler
어셈블리CommonGameSystem.Core
시작부팅 시 자동 등록 — 별도 설정 불필요

주요 기능

Scheduler는 액션을 나중에 실행합니다: 지연 후 한 번(After), 고정 간격으로 반복(Every), 다음 프레임에(NextFrame), 또는 백그라운드 스레드에서 포스트했을 때 메인 스레드에서(Post / RunOnMain).

모든 타이머는 클럭Time 서비스가 소유한 이름 있는 시간 흐름 — 에 바인딩됩니다. 세 가지가 있습니다: Gameplay는 게임이 일시정지되면 멈추고 슬로 모션에서 느려지며, UI는 일시정지 메뉴 중에도 계속 돌고, Background는 항상 돕니다. Gameplay 클럭 위의 쿨다운 타이머는 메뉴가 열려 있는 동안 자동으로 멈춥니다 — 일시정지 로직을 전혀 쓸 필요가 없습니다.

Post는 프레임워크 타이밍 스택에서 유일한 스레드 안전 진입점이므로, 백그라운드 작업(파일 로딩, 네트워크 호출)이 Unity API를 스레드 밖에서 건드리지 않고도 결과를 메인 스레드로 넘길 수 있습니다.

빠른 시작

Awake()Start()에서 한 번 리졸브하고 참조를 캐시하세요:

using CommonGameSystem.Core;
using UnityEngine;

[DefaultExecutionOrder(100)] // Run after the framework has started.
public class MyTimerUser : MonoBehaviour
{
    private IScheduler _scheduler;

    private void Awake()
    {
        _scheduler = ServiceLocator.Resolve<IScheduler>();
    }
}

SLServiceLocator의 짧은 별칭입니다: var scheduler = SL.Resolve<IScheduler>();

API 레퍼런스

원샷 지연

각 호출은 토큰을 반환합니다. 타이머가 발화하기 전에 취소하려면 토큰을 dispose하세요.

멤버비고
IDisposable After(float delaySeconds, Action callback)지연 후 한 번 발화합니다. Gameplay 클럭에서(게임과 함께 일시정지).
IDisposable After(float delaySeconds, Clock clock, Action callback)지연 후 한 번 발화합니다. 선택한 클럭에서.

반복 타이머

타이머를 멈추려면 반환된 토큰을 반드시 dispose해야 합니다.

멤버비고
IDisposable Every(float intervalSeconds, Action callback)Gameplay 클럭에서 고정 간격으로 반복합니다.
IDisposable Every(float intervalSeconds, Clock clock, Action callback)선택한 클럭에서 고정 간격으로 반복합니다.

다음 프레임

시간 기반이 아니라 프레임 기반입니다 — 게임이 일시정지된 동안에도 실행됩니다.

멤버비고
IDisposable NextFrame(Action callback)다음 프레임에 콜백을 실행합니다.

백그라운드 스레드에서 메인 스레드로 (스레드 안전)

멤버비고
void Post(Action action)어느 스레드에서든 액션을 큐에 넣습니다. 큐에 들어간 액션은 다음 메인 스레드 틱에, 포스트된 순서대로 실행됩니다.
void RunOnMain(Action action)메인 스레드에서는 액션을 즉시 실행합니다. 백그라운드 스레드에서는 Post처럼 큐에 넣습니다. 다른 스케줄된 콜백 안에서 호출해도 안전합니다.

진단

멤버비고
int PendingCount { get; }살아 있는 타이머와 큐에 있는 포스트의 대략적인 개수. 프로파일링 전용.

예제

using System;
using CommonGameSystem.Core;
using UnityEngine;

[DefaultExecutionOrder(100)]
public class CooldownManager : MonoBehaviour
{
    [SerializeField] private GameObject enemyPrefab; // assign in the Inspector

    private IScheduler _scheduler;
    private IDisposable _aiLoopToken;

    private void Awake()
    {
        _scheduler = ServiceLocator.Resolve<IScheduler>();
    }

    // One-shot: "in 2 seconds, spawn an enemy".
    public void SpawnEnemyIn2Seconds()
    {
        _scheduler.After(2f, () => Instantiate(enemyPrefab));
    }

    // Repeating: "tick the AI every 0.5 seconds; pause when the game pauses".
    public void StartAiLoop()
    {
        _aiLoopToken = _scheduler.Every(0.5f, Clock.Gameplay, TickAi);
    }

    private void TickAi()
    {
        // Runs every 0.5 game-seconds. Pauses automatically while menus are open.
    }

    // Next frame: "refresh the UI on the next frame".
    public void RefreshUiNextFrame()
    {
        _scheduler.NextFrame(RefreshUi);
    }

    private void RefreshUi()
    {
        // Update health bars, labels, and so on.
    }

    // Background thread to main thread: "loading finished, update the UI safely".
    public void StartAsyncLoad()
    {
        System.Threading.Tasks.Task.Run(() =>
        {
            string data = LoadDataExpensively();
            _scheduler.Post(() => OnDataLoaded(data)); // safe from any thread
        });
    }

    private string LoadDataExpensively() => "loaded"; // your real loading work here

    private void OnDataLoaded(string data)
    {
        // Update the UI with the loaded data.
    }

    // Stop the repeating timer when this object goes away.
    private void OnDestroy()
    {
        _aiLoopToken?.Dispose();
    }
}

끄기

ServiceLocator.Replace<IScheduler>(new NullScheduler());

모든 스케줄링이 무음이 됩니다. After, Every, NextFrame은 아무것도 등록하지 않고 아무것도 하지 않는 토큰을 반환합니다. PostRunOnMain은 액션을 버립니다. PendingCount는 0입니다. 스트레스 테스트나 스케줄된 로직을 일시적으로 끌 때 유용합니다. 첫 호출이 경고를 로그하므로, 실수로 바꿔치기해도 눈에 띕니다.

흔한 함정

  • 올바른 클럭을 고르세요. 잘못된 클럭은 조용히 실패합니다 — 오류 없이 일시정지 동작만 틀립니다. 쿨다운과 AI에는 Clock.Gameplay(기본값)를 쓰세요. 일시정지 중에도 계속 돌아야 하는 메뉴·HUD 애니메이션에는 Clock.UI를 쓰세요. 음악 페이드처럼 항상 도는 작업에는 Clock.Background를 쓰세요. 전체 클럭 계약은 Time 서비스를 보세요.
  • Every는 dispose가 필수입니다. 반환된 토큰을 버리면 타이머가 영원히 돕니다. 필드에 저장했다가 끝났을 때 — 보통 OnDestroy()에서 — Dispose()를 호출하세요.
  • Post가 유일한 스레드 안전 진입점입니다. 메인 스레드 밖에서 쓸 수 있는 것은 Post()와, 백그라운드 스레드에서 호출된 RunOnMain()뿐입니다. 그 외 모든 메서드는 워커 스레드에서 호출하면 InvalidOperationException을 던집니다.
  • 콜백은 프레임 위에서 실행됩니다. 한 콜백에서 난 예외는 로그되고 격리됩니다 — 다른 타이머는 여전히 발화합니다. 다만 느린 콜백은 여느 메인 스레드 작업처럼 프레임을 지연시킵니다.
  • NullSchedulerPost까지 포함해 전부 무음으로 만듭니다. 백그라운드→메인 디스패치에 의존한다면, 스케줄러를 NullScheduler로 교체하는 순간 그 결과들이 조용히 버려집니다.

관련 페이지

  • Time — 클럭, 일시정지, 타임 스케일
  • Tween — 같은 클럭 위에서의 값 애니메이션