C
カスタムキューの実装
このトピックでは、Qt Quick Ultralite 向けのカスタムキューを実装する方法について説明します。
概要
キューは、Qt Quick Ultraliteにおいてイベントを処理する上で重要な要素です。その最も顕著な例の一つが、入力処理におけるキューの使用です。EventQueue を使用することで、イベントをQt Quick Ultraliteに伝播させ、Qt Quick Ultraliteの実行サイクル中の適切なタイミングで処理されるようにすることができます。
デフォルトのキュー実装
EventQueue のデフォルトのキュー実装はDoubleQueue であり、platform\common\baremetal\doublequeue.cpp にあります。これは、ライターが1つのみである限り、割り込み安全です。複数のライターが存在する場合、EventQueue::postEvent()は別のEventQueue::postEvent()によって割り込まれてはなりません。さらに、DoubleQueue とのスレッドセーフ性は保証できないため、その使用にも制限があります。
std::thread およびstd::mutex をサポートする組み込みプラットフォームでは、デフォルトのキューはミューテックスを使用してスレッドセーフになっています。
デフォルトのキュー実装を使用するには、プロジェクトにplatform\common\baremetal\doublequeue.cpp を追加してください。
注: デフォルトのキューを使用したい場合 、またはお使いの OS が OS 固有のキューを提供していない場合は、この実装をスキップして、次のトピック「タッチ入力の処理」にすぐに進むことができます。
OS キューの実装
ほとんどのオペレーティングシステムは独自のキューを提供しており、これには割り込み安全性やスレッド安全性といった追加の利点がある場合があります。また、複数のタスクを使用するアプリケーションで必要な、タスク間の適切な通信も可能にします。OSキューを使用することで、通常はDoubleQueue の制限により使用できないような場合でも、EventQueue を利用できるようになります。
このトピックでは、例えば、MessageQueueInterface API をオペレーティングシステムが提供するキューを使用するように適応させる方法について説明します。EventQueue のプラットフォーム実装により、Qt Quick Ultraliteでカスタムキューを使用することが可能になります。
カスタムキューの適応
カスタムキューの実装に使用されるキューの抽象化は、MessageQueueInterface API と呼ばれます。これには、 platform\messagequeue.h ヘッダーをインクルードすることでアクセスできます。このAPIは、抽象関数とデフォルトの実装が備わった関数を提供します。これらの関数は再実装する必要があります。詳細については、MessageQueueInterface クラスのドキュメントを参照してください。
注: ここに示すコードスニペットは 、Qt Quick Ultraliteのインストールディレクトリ(platform\boards\qt\example-baremetal\examplequeue.cpp )にある実装例からのものです。デモの目的上、キューのバックエンドとして単純な循環バッファを使用しています。
正常に動作する実装を実現するには、MessageQueueInterface API の以下の関数を実装する必要があります:
- コンストラクタ - 実装のコンストラクタは、キューが保持できるアイテムの最大数を表す整数を少なくとも1つ引数として受け取る必要があります。また、MessageQueueInterface::MessageQueueInterface() を呼び出す必要があります。
以下に、実装のコンストラクタの例を示します:
MyMessageQueue(const uint32_t &capacity, const uint32_t &messageSize) : MessageQueueInterface() , mQueue(NULL) , mOverrunFlag(false) { void *memory = qul_malloc(sizeof(Private::CircularBuffer)); mQueue = new (memory) Private::CircularBuffer(capacity, messageSize); } - MessageQueueInterface::discardSupported() およびMessageQueueInterface::overwriteSupported() - これらの関数は、キューの実装が破棄、上書き、あるいはその両方をサポートしているかどうかを示します。これらは、
trueまたはfalseのいずれかを返す必要があります。注: EventQueue が機能するためには、これらの関数のいずれかが
trueを返す必要があります。 - MessageQueueInterface::enqueueOrDiscard() - この関数は、指定されたメッセージをキューの末尾にプッシュします。キューが満杯の場合、メッセージは破棄され、オーバーラン状態が設定されなければなりません。メッセージがキューにプッシュされた場合はMessageQueueStatus::Success を返し、メッセージが破棄された場合はMessageQueueStatus::MessageDiscarded を返さなければなりません。
注: message 引数の内容は 、元のメッセージが削除される可能性があるため、キューにコピーする必要があります。
実装が破棄をサポートしていない場合、この関数は決して呼び出されるべきではありませんが、MessageQueueStatus::DiscardingNotSupported を返さなければなりません。
以下に、この関数の実装例を示す:
MessageQueueStatus enqueueOrDiscard(const void *message) QUL_DECL_OVERRIDE { if (mQueue->isFull()) { // Discard message mOverrunFlag = true; return MessageQueueStatus::MessageDiscarded; } mQueue->pushBack(message); return MessageQueueStatus::Success; } - MessageQueueInterface::enqueueOrOverwrite() - この関数は、指定されたメッセージをキューの末尾にプッシュします。キューが満杯の場合、キュー内の最も古いメッセージを指定されたメッセージで上書きし、オーバーラン状態を設定しなければなりません。メッセージのプッシュに成功した場合はMessageQueueStatus::Success を返し、古いメッセージが指定されたメッセージで上書きされた場合はMessageQueueStatus::MessageOverwritten を返さなければなりません。
注: enqueueOrDiscard()と同様に 、message の内容は、元のメッセージが削除される可能性があるため、キューにコピーする必要があります。
実装が上書きをサポートしていない場合、この関数は決して呼び出されることはありませんが、MessageQueueStatus::OverwritingNotSupported を返さなければなりません。
EventQueue イベント型がポインタの場合、上書きはサポートされません。この機能が必要な場合は、実装側で上書きされるポインタに対して適切なメモリ処理を行う必要があります。
以下は、上書きがサポートされていない実装におけるMessageQueueInterface::enqueueOrOverwrite()の例です:
MessageQueueStatus enqueueOrOverwrite(const void *message) QUL_DECL_OVERRIDE { return MessageQueueStatus::OverwriteNotSupported; } - MessageQueueInterface::receive() - 指定されたタイムアウト時間内にキューからメッセージを1つ取り出し、それを返します。タイムアウトはミリ秒単位で指定されます。タイムアウト値が0の場合、関数は一切待機しませんが、負の値が指定された場合は、関数はメッセージが到着するまで無期限に待機します。
キューからメッセージの取得に成功した場合、message 引数には取得したメッセージが含まれていなければなりません(つまり、取得したメッセージの内容がmessage が指すアドレスにコピーされている必要があります)。また、関数はMessageQueueStatus::Success を返さなければなりません。キューが空だった場合は、代わりにMessageQueueStatus::EmptyQueue またはMessageQueueStatus::Timeout を返す必要があります。
以下は、MessageQueueInterface::receive() の実装例です:
MessageQueueStatus receive(void *message, int32_t timeout = 0) QUL_DECL_OVERRIDE { (void) timeout; // This example does not implement timeout handling. if (mQueue->isEmpty()) return MessageQueueStatus::EmptyQueue; mQueue->popFront(message); return MessageQueueStatus::Success; } - MessageQueueInterface::isEmpty() - キューが空の場合は `
true` を返し、それ以外の場合は `false` を返します。 - MessageQueueInterface::isOverrun() およびMessageQueueInterface::clearOverrun() - これらの関数は、キューのオーバーラン状態を返したり変更したりします。
bool isOverrun() const QUL_DECL_OVERRIDE { return mOverrunFlag; } void clearOverrun() QUL_DECL_OVERRIDE { mOverrunFlag = false; }
また、メッセージのエンキューおよび受信の割り込みセーフ版を実装するための関数も用意されています。これらのメソッドには、Qt Quick Ultralite が提供する対応する関数を呼び出すデフォルトの実装があります。ただし、割り込みセーフ性を確保するために、以下の関数を再実装することを推奨します:
- MessageQueueInterface::enqueueOrDiscardFromInterrupt() - この関数は、MessageQueueInterface::enqueueOrDiscard() の割り込みセーフ版です。
以下に、その実装例を示します:
MessageQueueStatus enqueueOrDiscardFromInterrupt(const void *message) QUL_DECL_OVERRIDE { // disableInterrupts(); MessageQueueStatus state = enqueueOrDiscard(message); // enableInterrupts(); return state; } - MessageQueueInterface::enqueueOrOverwriteFromInterrupt() - この関数は、MessageQueueInterface::enqueueOrOverwrite() の割り込み安全版です。
- MessageQueueInterface::receiveFromInterrupt() - この関数は、MessageQueueInterface::receive() の割り込みセーフ版です。
注: MessageQueueInterface::receiveFromInterrupt() は タイムアウト引数を受け取りますが、 ほとんどの中断呼び出しでは、可能であれば待機を避ける必要があります。
以下に、この関数の実装例を示します。
MessageQueueStatus receiveFromInterrupt(void *message, int32_t timeout = 0) QUL_DECL_OVERRIDE { (void) timeout; // This example does not implement timeout handling. // disableInterrupts(); MessageQueueStatus state = receive(message); // enableInterrupts(); return state; }注: この実装例では、
timeout引数は使用 されていません。 - MessageQueueInterface::isEmptyFromInterrupt() - この関数は、MessageQueueInterface::isEmpty() の割り込みセーフ版です。
これで、カスタムキューを使用したMessageQueueInterface API の動作する実装が完成したはずです。EventQueue は、MessageQueue という利便性APIを使用してキュー実装と連携します。ただし、MessageQueue 自体は、MessageQueueInterface を実装していること以外、カスタム実装に関する情報を一切持ちません。その代わりに、MessageQueue はrequestQueue()関数を呼び出し、その用途に適したキューを取得します。 この関数は、capacity とmessageSize という引数を受け取ります。これらは、それぞれキューが保持できる項目の数と、キューが使用するメッセージのサイズを示します。この関数は、キュー実装のインスタンスへのポインタを返すか、要求された容量やメッセージサイズをサポートしていない場合はNULLポインタを返さなければなりません。
requestQueue() の実装例:
MessageQueueInterface *requestQueue(size_t queueCapacity, size_t messageSize)
{
void *queue = qul_malloc(sizeof(MyMessageQueue));
if (queue == NULL) {
return NULL;
}
MessageQueueInterface *interface = new (queue) MyMessageQueue(queueCapacity, messageSize);
return interface;
}キューが不要になった場合、MessageQueue はdeleteQueue()を呼び出し、これによりキューリソースの削除とメモリ解放が行われます。
deleteQueue() の実装例:
void deleteQueue(MessageQueueInterface *queue)
{
MyMessageQueue *mq = static_cast<MyMessageQueue *>(queue);
mq->~MyMessageQueue();
qul_free(mq);
}MessageQueue maximumQueueMessageSize() を実装する際は、許可される最大メッセージサイズを把握しておく必要があります。これにより、指定されたメッセージサイズが実装のサイズ制限を超える状況に対処できます。この情報は、 () を実装することで提供できます。この関数は、サポートされる最大サイズをバイト単位で返すか、キューが任意のメッセージサイズをサポートする場合は を返す必要があります。 が返された場合、メッセージのサイズにかかわらず、実装側がメッセージの処理を担当します。SIZE_MAX SIZE_MAX
以下の例は、maximumQueueMessageSize() を実装したもので、SIZE_MAX を返します。
size_t maximumQueueMessageSize()
{
return LONG_MAX;
}特定のQtライセンスの下で利用可能です。
詳細はこちら。