個人的なメモ

Tomohiro Suzuki @hiro128_777 のブログです。Microsoft MVP for Developer Technologies 2017- 本ブログと所属組織の公式見解は関係ございません。

Cocos Sharp Deep Dive(2) スケジューラ

今回は、Cocos Sharp の もう一つの重要な機能であるスケジューラがどのような内部動作をしているのかを深く解析したいと思います。

スケジューラにメソッドを登録する場合には、以下のようにCCNodeScheduleメソッドを使用します。

Schedule(t => this.DetectCollisions(), 0.1f);

これは、0.1秒ごとにDetectCollisionsメソッドを実行しなさいという意味です。

まずは、Scheduleメソッドの内部動作を確認してみましょう。

CCNodeScheduleメソッドのソースコードを見てみると以下のようになっています。

public void Schedule (Action<float> selector, float interval)
{
	Schedule (selector, interval, CCSchedulePriority.RepeatForever, 0.0f);
}

Scheduleメソッドのオーバーロードをコールしていますので、コール先のソースコードを見てみると以下のようになっています。

public void Schedule (Action<float> selector, float interval, uint repeat, float delay)
{
	if (Scheduler != null)
		Scheduler.Schedule (selector, this, interval, repeat, delay, !IsRunning);
	else
		AddLazySchedule (selector, this, interval, repeat, delay, !IsRunning);
}

SchedulerプロパティのScheduleメソッドをコールしています。AddLazyScheduleの方も追っていくと、最終的にはScheduleメソッドをコールするようになっています。

Schedulerプロパティのを確認すると、その実体はCCScheduler.SharedSchedulerのようです。

CCScheduler Scheduler
{
	get { return CCScheduler.SharedScheduler; }
}

SharedSchedulerというプロパティ名からもわかるとおり、CCSchedulerを確認すると Singleton になっています。

さらに、Scheduleメソッドを確認すると以下のように、デリゲートと付帯情報をCCTimerのリストとして登録しています。

public void Schedule (Action<float> selector, ICCUpdatable target, float interval, uint repeat, float delay, bool paused)
{
	// 省略
	element.Timers.Add(new CCTimer(this, target, selector, interval, repeat, delay));
	// 省略
}

よって、CCNodeScheduleメソッドに登録したデリゲートは全てCCSchedulerで一括管理されるようになっていることがわかります。

では次に一括管理されたデリゲートがどのように実行されていくか見てみましょう。

デリゲートと付帯情報はCCTimerのリストとして管理されており、CCSchedulerUpdateメソッド内で、各CCTimerUpdateメソッドがコールされています。

public class CCScheduler
{
	internal void Update (float dt)
	{
		if (TimeScale != 1.0f)
		{
			dt *= TimeScale;
		}

		// 省略

		for (elt.TimerIndex = 0; elt.TimerIndex < elt.Timers.Count; ++elt.TimerIndex)
		{
			elt.CurrentTimer = elt.Timers[elt.TimerIndex];
			if(elt.CurrentTimer != null) {
				elt.CurrentTimerSalvaged = false;

				elt.CurrentTimer.Update(dt);

				elt.CurrentTimer = null;
			}
		}
		// 省略
	}
}

CCTimerUpdateメソッドを確認してみると以下のようになっています。

internal class CCTimer : ICCUpdatable
{
	public void Update(float dt)
	{
	    if (elapsed == -1)
	    {
	        elapsed = 0;
	        timesExecuted = 0;
	    }
	    else
	    {
	        if (runForever && !useDelay)
	        {
	            //standard timer usage
	            elapsed += dt;
	            if (elapsed >= Interval)
	            {
	                if (Selector != null)
	                {
	                    Selector(elapsed);
	                }
					elapsed = 0;
	            }
	        }
	        else
	        {
				// 省略
	        }
	    }
	}
}

今回のフレームの経過時間であるdtから、前回デリゲートを実行してからのトータル経過時間elapsedを計算して、与えられたIntervalを経過していたら、Selectorデリゲートを実行しているのが分かります。

これで、スケジューラがどのような内部動作をしているのかがわかりました。

というわけで、今回はここまでです。