8. 時間

3DS で扱うことのできる時間には、システムクロックをもとに計測した時間であるチックとバッテリバックアップ付きで時刻を計時する RTC の 2 種類があります。

アプリケーションに時間の経過を知らせる機能としては、チックを利用したタイマとアラームがあります。

RTC は現在時刻を取得する機能のほかにも、設定された日時にアラームを鳴らす機能を持っています。

8.1. 時間を表すクラス

SDK 内で時間を指定する関数は、単位を間違えないためにも、その引数に nn::fnd::TimeSpan クラスを使うように設計されています。このクラスの内部では、時間はナノ秒単位 の 64 bit 整数として表されています。単位があいまいになるのを防ぐため、整数値からこの型への暗黙的な変換は用意されていませんが、0 だけはこの型への暗黙的な変換を行うことができます。

nn::fnd::TimeSpan クラスには、各単位(秒、ミリ秒、マイクロ秒、ナノ秒)で表された時間の整数値からインスタンスを生成する static メンバ関数(From*Seconds())と、インスタンスが保持している時間を各単位での値として s64 型で取得するメンバ関数(Get*Seconds())が用意されています。

また、このクラス同士であれば比較(==, !=, <, >, <=, >=)と加減算(+, -, +=, -=)を行うことができます。

コード 8-1. 各時間単位からのインスタンス生成関数と時間の取得関数
// From*Seconds()
static nn::fnd::TimeSpan FromSeconds(s64 seconds);
static nn::fnd::TimeSpan FromMilliSeconds(s64 milliSeconds);
static nn::fnd::TimeSpan FromMicroSeconds(s64 microSeconds);
static nn::fnd::TimeSpan FromNanoSeconds(s64 nanoSeconds);
// Get*Seconds()
s64 GetSeconds() const;
s64 GetMilliSeconds() const;
s64 GetMicroSeconds() const;
s64 GetNanoSeconds() const;

8.2. チック

チックは CPU が 268 MHz で動作しているときの 1 クロックの時間(約 3.73 ナノ秒)で、その値をチック値と表記することもあります。1 秒間のチック値は nn::os::Tick::TICKS_PER_SECOND に定数として定義されています。

注意:

SNAKE が拡張モードで動作している場合でも、1 チックは標準モードで動作している場合と同じ時間です。つまり、拡張モードでは 1 チックが 3 CPU サイクル分の時間となります。

チック値は nn::os::Tick クラスで扱うことができ、変換コンストラクタや変換演算子によって実際の時間を表す nn::fnd::TimeSpan クラスと相互に変換することができます。

コンストラクタには、s64 型で表されたチック値からインスタンスを生成するコンストラクタと、nn::fnd::TimeSpan クラスで表された時間をチック値に変換してインスタンスを生成するコンストラクタの 2 種類があります。

変換演算子には、チック値を s64 型の数値に変換するものと、nn::fnd::TimeSpan クラスに変換するものの 2 種類があります。nn::os::Tick クラス同士であれば加減算(+, -, +=, -=)を行うことができます。

上記以外にも、ToTimeSpan() を呼び出すことで、チック値から変換された時間を表す nn::fnd::TimeSpan クラスのインスタンスを生成することができます。

GetSystemCurrent() を呼び出すことで、システムが起動してから経過した時間をチック値(nn::os::Tick クラス)で取得することができます。

8.3. タイマ

タイマは、指定された時間が経過したことを通知する機能です。タイマはシグナル状態と非シグナル状態の 2 つの状態を持ち、指定された時間が経過すると非シグナル状態からシグナル状態に遷移します。タイマの作成個数は 8 個に制限されています。

タイマは nn::os::Timer クラスで定義されています。インスタンスを生成して Initialize() または TryInitialize() を呼び出して初期化します。初期化の際には、シグナルリセットの種類を手動リセットと自動リセットから選択することができます。手動リセットの場合、シグナル状態になるとその状態はクリアされるまで維持され、その間、そのタイマがシグナル状態になるのを待っているスレッドがすべて解放されます。自動リセットの場合、シグナル状態になると、そのタイマがシグナル状態になるのを待っているスレッドのうちで優先順位の一番高いスレッドだけが解放され、すぐに非シグナル状態に戻ります。

タイマの種類には、開始から指定された時間が経過すると 1 度だけシグナル状態に遷移するワンショットタイマと、指定された時間が経過したあとも一定間隔でシグナル状態に遷移する周期的タイマの 2 種類があります。ワンショットタイマで開始するには StartOneShot() を呼び出します。周期的タイマで開始するには StartPeriodic() を呼び出します。どちらのタイマも Stop() を呼び出して停止させることができます。

Wait() の呼び出しで、スレッドはタイマがシグナル状態になるまで待ちます。時間の経過を待たずにタイマをシグナル状態に遷移させるには Signal() を呼び出します。シグナル状態に遷移すると、シグナルリセットの種類が手動リセットの場合は、ClearSignal() を呼び出すまでシグナル状態が維持されます。

インスタンスを明示的に破棄する場合は Finalize() を呼び出してください。

タイマを使用するアプリケーションは「5.3.1.1. 終了処理の注意事項」を参照し、終了処理でインスタンスの破棄などを確実に行ってください。

8.4. アラーム

アラームは、指定された時間が経過すると登録されたハンドラを呼び出す機能です。アラームは内部でスレッドを作成してハンドラを呼び出します。そのため、使用する前に nn::os::InitializeAlarmSystem() でアラームシステムを初期化していなければなりません。

アラームは nn::os::Alarm クラスで定義されています。インスタンスを生成して Initialize() または TryInitialize() を呼び出して初期化します。

アラームの種類には、開始から指定された時間が経過すると 1 度だけハンドラを呼び出すワンショットアラームと、指定された時間が経過したあとも一定間隔でハンドラを呼び出す周期的アラームの 2 種類があります。ワンショットアラームを設定するには SetOneShot() を呼び出します。周期的アラームを設定するには SetPeriodic() を呼び出します。どちらのアラームも Cancel() を呼び出して解除することができます。

アラームを設定することができるか状況かどうかは CanSet() の呼び出しで取得することができます。アラームが設定されていて、今後ハンドラが呼び出される場合は false を返します。Cancel() で解除しても、一度ハンドラが呼び出されるまで true は返されません。

ハンドラの型は以下のように定義されています。

コード 8-2. アラームハンドラの型定義
typedef void(* nn::os::AlarmHandler)(void *param, bool cancelled);

param にはアラームを設定したときの引数が渡されます。通常、cancelled には false が渡されますが、Cancel() でアラームが解除されたあとにハンドラが呼び出された場合は true が渡されます。

インスタンスを明示的に破棄する場合は Finalize() を呼び出してください。

注意:

現在の実装では、アラームシステムは 2 つの内部スレッドで機能しています。そのため、処理に長い時間のかかるハンドラが多数登録されていると、アラームシステムが不安定になります。

アラームを使用するアプリケーションは「5.3.1.1. 終了処理の注意事項」を参照し、終了処理でインスタンスの破棄などを確実に行ってください。

8.5. RTC

RTC は Real Time Clock の略で、時刻の計時を行うハードウェアのことを指します。バッテリバックアップにより、本体の電源が落とされていても時刻の計時は継続されます。

時刻の設定は本体設定からのみ行うことができます。設定可能な日付は 2049/12/31 までですが、計時可能な日付は 2099/12/31 までです。時刻の設定から 50 年以上経たないと計時の上限に達することがないため、アプリケーション起動中の時刻の巻き戻りをチェックする必要はありません。

この節では、RTC で計時されている時刻をアプリケーションで利用する方法について説明します。

8.5.1. 日時を表すクラス

CTR-SDK では日時を表すクラスとして nn::fnd::DateTime クラスを用意しています。RTC で計時されている現在時刻は nn::fnd::DateTime::GetNow() の呼び出しで取得することができ、返されるのはこのクラスのインスタンスとなっています。

引数ありのコンストラクタでは、年月日と時分秒だけでなく、ミリ秒まで指定可能です。引数なしのコンストラクタでは 2000/01/01 00:00:00.000 を表すインスタンスが生成されます。

このクラスで扱うことのできる日時は nn::fnd::DateTime::MIN_DATE_TIME(1900/01/01 00:00:00.000)から nn::fnd::DateTime::MAX_DATE_TIME(2189/12/31 23:59:59.999)までです。2000/01/01 (Sat) を基準とするグレゴリオ暦ですべての年が計算され、1 日が正確に 86400 秒であるという前提で日時を扱います。

nn::fnd::DateTime クラス同士の減算では、2 つの日時の差異が nn::fnd::TimeSpan クラスで返されます。また、nn::fnd::DateTime クラスと nn::fnd::TimeSpan クラスとの加減算は、ある日時から指定時間が経過した(戻った)日時を nn::fnd::DateTime クラスで返します。

日時のパラメータには、年、月、日、曜日、時(24 時間制)、分、秒、ミリ秒があり、それぞれ Get*() で取得、曜日を除くパラメータは Replace*() で書き換えを行うことができます。各パラメータに対する Get*() は、曜日のみが列挙型(nn::fnd::Week)を、そのほかは s32 型を返します。Replace*() は書き換えたパラメータを持つ新しいインスタンスを返します。元のインスタンスのパラメータは書き換えられません。

nn::fnd::DateTimeParameters 構造体を引数に持つ GetParameters()FromParameters() では、すべてのパラメータの取得および書き換えを一括して行うことができます。FromParameters() の呼び出しでは、構造体の曜日を示すメンバ変数は無視されます。無効な日時のパラメータで書き換えた場合の結果は不定です。有効な日時のパラメータであるかどうかは IsValidParameters() で確認することができます。構造体を引数にした関数では曜日も判定基準に入っていますので、構造体のパラメータ設定には注意が必要です。曜日が不明である場合は、曜日以外の各パラメータを引数に持つ関数で確認してください。

DateToDays() では、指定された日付が基準日(2000/01/01)から何日後であるかを取得することができます。無効な日付で問い合わせた場合の結果は不定です。有効な日付であるかどうかは IsValidDate() で確認することができます。また、指定された年がうるう年であるかどうかは IsLeapYear() で確認することができ、うるう年ならば 1 を返します。

DaysToDate() では、基準日からの経過日数から日付を取得することができます。DaysToWeekday() では、基準日からの経過日数から曜日を取得することができます。

8.5.2. RTC アラーム機能

RTC で計時されている時刻を利用した RTC アラーム機能が用意されています。この機能は、現在時刻に分単位の変化が起こったときに設定された時刻を過ぎているとアプリケーションへの通知を行います。時刻の設定によっては 1 分少々遅れて通知される可能性があります。そのため、目覚まし時計のように指定時刻を通知する用途には向いていますが、砂時計のように時間経過を通知する用途には向いていません。時間経過を通知する用途には、「8.3. タイマ」または「8.4. アラーム」を利用してください。

RTC アラーム機能は、PTM ライブラリを介して使用することができます。PTM ライブラリを使用するには、nn::ptm::Initialize() を呼び出して初期化を行う必要があります。PTM ライブラリの使用を終了するときは nn::ptm::Finalize() を呼び出してください。

コード 8-3. PTM ライブラリの初期化と終了
nn::Result nn::ptm::Initialize();
nn::Result nn::ptm::Finalize();

RTC アラーム機能では、設定された時刻を過ぎたときに、指定されたイベントがシグナル状態になることでアプリケーションへの通知が行われます。

注意:

スリープ中は通知を受け取ることができません。HOME メニューやライブラリアプレットを起動した状態では、HOME メニューやライブラリアプレットを起動したスレッドが停止されたまま、イベントがシグナル状態になります。グラフィックスやサウンドを操作する権限のない状態で通知を受け取るため、RTC アラームを待ち受けるスレッドの実装には注意が必要です。

イベントの指定やアラーム時刻の設定などは、以下の関数で行います。

コード 8-4. RTC アラーム機能の関数
nn::Result nn::ptm::RegisterAlarmEvent(nn::os::Event &event);
nn::Result nn::ptm::SetRtcAlarm(nn::fnd::DateTime datetime);
nn::Result nn::ptm::GetRtcAlarm(nn::fnd::DateTime *pDatetime);
nn::Result nn::ptm::CancelRtcAlarm();

RTC アラーム機能の通知を受け取るイベントは nn::ptm::RegisterAlarmEvent() で指定します。event に渡す nn::os::Event クラスのインスタンスは、アプリケーションで生成および初期化を行ってください。

アラーム時刻の設定は nn::ptm::SetRtcAlarm() で行います。datetime には、アラーム時刻を nn::fnd::DateTime クラスで指定しますが、その時刻は分単位で設定してください。すでにアラーム時刻が設定されていた場合は nn::ptm::ResultOverWriteAlarm を返しますが、アラーム時刻の設定には成功しています。過去の時刻を設定しても、すぐには通知されません。

アラーム時刻の現在の設定は nn::ptm::GetRtcAlarm() で取得することができます。pDatetime には、アラーム時刻を受け取る nn::fnd::DateTime クラスのインスタンスへのポインタを渡してください。アラーム時刻が設定されていなかった場合は、nn::ptm::ResultNoAlarm を返します。

RTC アラームの設定をキャンセルする場合は nn::ptm::CancelRtcAlarm() を呼び出してください。アラーム時刻が設定されていなかった場合は、nn::ptm::ResultNoAlarm を返します。

8.5.3. 時刻改変のオフセット値の取り扱い

ユーザーが本体設定の「日付と時刻」で時刻を何秒進めたか、もしくは何秒戻したかを、秒単位のオフセット値の累積をハードウェアに記録しています。その情報は nn::cfg::GetUserTimeOffset() で取得することができます。詳細については「11.2.8. RTC 改変オフセット値」を参照してください。

同一の本体でアプリケーションを起動した場合に限り、このオフセット値を前回起動時の値と比較することで、ユーザーが時刻の設定を変更したかどうかを判別することができます。この判別の結果をアプリケーションで利用することができますが、別の本体でアプリケーションを起動した場合でもゲームの進行に不都合がないようにしてください。

8.6. チックと RTC の混在使用の禁止

チック(nn::os::Tick クラス)と RTC(nn::fnd::DateTime クラス)は同じように時間を扱っていますが、時間の進み具合や精度が異なります。つまり、同じ期間をチックと RTC で計測しても、その結果が同じ値になるとは限りません。そのため、チックで得た時間と RTC で得た時間を混在して使用してはいけません。

チックには個体差があり、温度変化の影響を大きく受けます。また、1 ヶ月に ±300 秒程度の誤差が生じる可能性があります。

RTC にも個体差があり、温度変化の影響を大きく受けます。また、1 ヶ月に ±60 秒程度の誤差が生じる可能性があります。なお、nn::fnd::DateTime::GetNow() は計算により補間された現在時刻を返すため、進む速度には 1 時間当たり ±1 秒程度の揺らぎがあります。

サウンドや動画のストリーミング再生など、ライブラリ内で時間を扱っている場合の多くは、その時間の進む速度がチックや RTC の速度とは異なります。そのため、アプリケーションでの演出とサウンド再生の同期を取りたい場合には、個々のライブラリの仕様を把握して、速度が一致する時間を使用してください。