mirror of
https://github.com/thunderbrewhq/squall.git
synced 2026-02-04 08:59:07 +00:00
feat(event): implement all SEvt functions
This commit is contained in:
parent
ff0b43c2ce
commit
7c72c25553
5 changed files with 1153 additions and 0 deletions
437
storm/Event.cpp
Normal file
437
storm/Event.cpp
Normal file
|
|
@ -0,0 +1,437 @@
|
||||||
|
#include "Event.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "list/TSList.hpp"
|
||||||
|
#include "thread/CCritSect.hpp"
|
||||||
|
#include "Atomic.hpp"
|
||||||
|
#include "Error.hpp"
|
||||||
|
#include "Memory.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
struct BREAKCMD : public TSLinkedNode<BREAKCMD> {
|
||||||
|
void* data;
|
||||||
|
};
|
||||||
|
|
||||||
|
static CCritSect s_critsect;
|
||||||
|
static TSList<BREAKCMD, TSGetLink<BREAKCMD>> s_breakcmdlist;
|
||||||
|
static int32_t s_modified;
|
||||||
|
static ATOMIC32 s_dispatchesinprogress;
|
||||||
|
|
||||||
|
|
||||||
|
struct _IDHASHENTRY {
|
||||||
|
uint32_t id;
|
||||||
|
uint32_t sequence;
|
||||||
|
SEVTHANDLER handler;
|
||||||
|
_IDHASHENTRY* next;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct _IDHASHTABLE {
|
||||||
|
_IDHASHENTRY** data;
|
||||||
|
uint32_t size;
|
||||||
|
uint32_t used;
|
||||||
|
_IDHASHTABLE* next;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct _TYPEHASHENTRY {
|
||||||
|
uint32_t type;
|
||||||
|
uint32_t subtype;
|
||||||
|
uint32_t sequence;
|
||||||
|
_IDHASHTABLE* idhashtable;
|
||||||
|
_TYPEHASHENTRY* next;
|
||||||
|
};
|
||||||
|
|
||||||
|
static _TYPEHASHENTRY **s_typehashtable;
|
||||||
|
static uint32_t s_typehashtablesize;
|
||||||
|
static uint32_t s_typehashtableused;
|
||||||
|
|
||||||
|
// Fake function to make tests not bleed into each other
|
||||||
|
void SEvtCleanExtraDataForTests() {
|
||||||
|
s_breakcmdlist.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeleteIdHashTable(_IDHASHTABLE* pTable) {
|
||||||
|
for (uint32_t i = 0; i < pTable->size; i++) {
|
||||||
|
for (_IDHASHENTRY* pEntry = pTable->data[i]; pEntry; pEntry = pTable->data[i]) {
|
||||||
|
pTable->data[i] = pEntry->next;
|
||||||
|
delete pEntry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SMemFree(pTable->data, __FILE__, __LINE__, 0);
|
||||||
|
delete pTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
_TYPEHASHENTRY* FindTypeHashEntry(uint32_t type, uint32_t subtype) {
|
||||||
|
if (!s_typehashtable || s_typehashtablesize == 0) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (_TYPEHASHENTRY* pEntry = s_typehashtable[(s_typehashtablesize - 1) & (subtype ^ type)]; pEntry; pEntry = pEntry->next) {
|
||||||
|
if (pEntry->type == type && pEntry->subtype == subtype) {
|
||||||
|
return pEntry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ComputeNewTableSize(uint32_t size) {
|
||||||
|
uint32_t result = 1;
|
||||||
|
while (result <= size * 2 + 2) {
|
||||||
|
result *= 2;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CopyIdHashTable(_IDHASHTABLE *dest, _IDHASHTABLE *source) {
|
||||||
|
dest->size = source->size;
|
||||||
|
dest->used = source->used;
|
||||||
|
dest->data = (_IDHASHENTRY**)STORM_ALLOC(sizeof(_IDHASHENTRY*) * dest->size);
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < source->size; i++) {
|
||||||
|
_IDHASHENTRY* pSourceData = source->data[i];
|
||||||
|
_IDHASHENTRY** ppDestData = &dest->data[i];
|
||||||
|
for (; pSourceData; pSourceData = pSourceData->next) {
|
||||||
|
_IDHASHENTRY* pNewEntry = STORM_NEW(_IDHASHENTRY);
|
||||||
|
|
||||||
|
*ppDestData = pNewEntry;
|
||||||
|
*pNewEntry = *pSourceData;
|
||||||
|
}
|
||||||
|
*ppDestData = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t SEvtBreakHandlerChain(void* data) {
|
||||||
|
s_critsect.Enter();
|
||||||
|
s_breakcmdlist.NewNode(2, 0, 0)->data = data;
|
||||||
|
s_critsect.Leave();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t SEvtDestroy() {
|
||||||
|
s_critsect.Enter();
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < s_typehashtablesize; i++) {
|
||||||
|
for (_TYPEHASHENTRY* pTypeEntry = s_typehashtable[i]; pTypeEntry; pTypeEntry = s_typehashtable[i]) {
|
||||||
|
for (_IDHASHTABLE* pTable = pTypeEntry->idhashtable; pTable; pTable = pTypeEntry->idhashtable) {
|
||||||
|
pTypeEntry->idhashtable = pTable->next;
|
||||||
|
DeleteIdHashTable(pTable);
|
||||||
|
}
|
||||||
|
s_typehashtable[i] = pTypeEntry->next;
|
||||||
|
delete pTypeEntry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s_typehashtable) {
|
||||||
|
delete s_typehashtable;
|
||||||
|
}
|
||||||
|
s_typehashtable = nullptr;
|
||||||
|
s_typehashtablesize = 0;
|
||||||
|
s_typehashtableused = 0;
|
||||||
|
|
||||||
|
s_modified = 1;
|
||||||
|
|
||||||
|
s_critsect.Leave();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t SEvtDispatch(uint32_t type, uint32_t subtype, uint32_t id, void* data) {
|
||||||
|
SInterlockedIncrement(&s_dispatchesinprogress);
|
||||||
|
|
||||||
|
int32_t success = 0;
|
||||||
|
uint32_t currsequence = -1;
|
||||||
|
_IDHASHENTRY *currptr = nullptr;
|
||||||
|
|
||||||
|
do {
|
||||||
|
s_critsect.Enter();
|
||||||
|
|
||||||
|
int32_t breakcmd = 0;
|
||||||
|
BREAKCMD* curr = s_breakcmdlist.Head();
|
||||||
|
int32_t iterate_delete = 0;
|
||||||
|
while (reinterpret_cast<intptr_t>(curr) > 0) {
|
||||||
|
if (curr->data == data) {
|
||||||
|
breakcmd = 1;
|
||||||
|
iterate_delete = -12;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (iterate_delete) {
|
||||||
|
curr = iterate_delete <= 0 ? nullptr : s_breakcmdlist.DeleteNode(curr);
|
||||||
|
iterate_delete = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
curr = s_breakcmdlist.RawNext(curr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (breakcmd) {
|
||||||
|
s_critsect.Leave();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!currptr || s_modified) {
|
||||||
|
currptr = nullptr;
|
||||||
|
auto typeentry = FindTypeHashEntry(type, subtype);
|
||||||
|
if (typeentry) {
|
||||||
|
_IDHASHTABLE* idhash = typeentry->idhashtable;
|
||||||
|
if (idhash->data && idhash->size != 0) {
|
||||||
|
for (currptr = idhash->data[id & (idhash->size - 1)]; currptr; currptr = currptr->next) {
|
||||||
|
if (currptr->id == id && currptr->sequence < currsequence) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s_dispatchesinprogress == 1) {
|
||||||
|
s_modified = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SEVTHANDLER handler = nullptr;
|
||||||
|
|
||||||
|
if (currptr) {
|
||||||
|
handler = currptr->handler;
|
||||||
|
currsequence = currptr->sequence;
|
||||||
|
do {
|
||||||
|
currptr = currptr->next;
|
||||||
|
} while (currptr && currptr->id != id);
|
||||||
|
}
|
||||||
|
|
||||||
|
s_critsect.Leave();
|
||||||
|
|
||||||
|
if (handler) {
|
||||||
|
success = 1;
|
||||||
|
handler(data);
|
||||||
|
}
|
||||||
|
} while(currptr);
|
||||||
|
|
||||||
|
SInterlockedDecrement(&s_dispatchesinprogress);
|
||||||
|
|
||||||
|
if (s_breakcmdlist.Head()) {
|
||||||
|
s_critsect.Enter();
|
||||||
|
|
||||||
|
BREAKCMD* ptr = s_breakcmdlist.Head();
|
||||||
|
int32_t iterate_delete = 0;
|
||||||
|
while (reinterpret_cast<intptr_t>(ptr) > 0) {
|
||||||
|
if (ptr->data == data) {
|
||||||
|
iterate_delete = 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (iterate_delete) {
|
||||||
|
ptr = iterate_delete <= 0 ? nullptr : s_breakcmdlist.DeleteNode(ptr);
|
||||||
|
iterate_delete = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ptr = s_breakcmdlist.RawNext(ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s_critsect.Leave();
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t SEvtPopState(uint32_t type, uint32_t subtype) {
|
||||||
|
int32_t success = 0;
|
||||||
|
s_critsect.Enter();
|
||||||
|
|
||||||
|
_TYPEHASHENTRY* typeentry = FindTypeHashEntry(type, subtype);
|
||||||
|
if (typeentry) {
|
||||||
|
_IDHASHTABLE *next = typeentry->idhashtable->next;
|
||||||
|
if (next) {
|
||||||
|
DeleteIdHashTable(typeentry->idhashtable);
|
||||||
|
typeentry->idhashtable = next;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// This WILL hang if called without recursive CCritSect.
|
||||||
|
// Provide a hack since it never gets called in WoW anyway.
|
||||||
|
// It also doesn't get called by SC for that matter. Nothing calls it. Classic.
|
||||||
|
#if !defined(WHOA_STORM_C_CRIT_SECT_RECURSIVE)
|
||||||
|
s_critsect.Leave();
|
||||||
|
success = SEvtUnregisterType(type, subtype);
|
||||||
|
s_critsect.Enter();
|
||||||
|
#else
|
||||||
|
success = SEvtUnregisterType(type, subtype);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s_modified = 1;
|
||||||
|
s_critsect.Leave();
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t SEvtPushState(uint32_t type, uint32_t subtype) {
|
||||||
|
int32_t success = 0;
|
||||||
|
s_critsect.Enter();
|
||||||
|
|
||||||
|
_TYPEHASHENTRY* pTypeHash = FindTypeHashEntry(type, subtype);
|
||||||
|
if (pTypeHash) {
|
||||||
|
_IDHASHTABLE* pNewTable = STORM_NEW(_IDHASHTABLE);
|
||||||
|
|
||||||
|
CopyIdHashTable(pNewTable, pTypeHash->idhashtable);
|
||||||
|
pNewTable->next = pTypeHash->idhashtable;
|
||||||
|
pTypeHash->idhashtable = pNewTable;
|
||||||
|
|
||||||
|
success = 1;
|
||||||
|
s_modified = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
s_critsect.Leave();
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t SEvtRegisterHandler(uint32_t type, uint32_t subtype, uint32_t id, uint32_t flags, SEVTHANDLER handler) {
|
||||||
|
STORM_VALIDATE_BEGIN;
|
||||||
|
STORM_VALIDATE(handler);
|
||||||
|
STORM_VALIDATE(!flags);
|
||||||
|
STORM_VALIDATE_END;
|
||||||
|
|
||||||
|
s_critsect.Enter();
|
||||||
|
|
||||||
|
_TYPEHASHENTRY* pTypeHash = FindTypeHashEntry(type, subtype);
|
||||||
|
if (!pTypeHash) {
|
||||||
|
if (s_typehashtableused >= s_typehashtablesize / 2) {
|
||||||
|
uint32_t newsize = ComputeNewTableSize(s_typehashtableused);
|
||||||
|
_TYPEHASHENTRY** pNewTable = static_cast<_TYPEHASHENTRY**>(STORM_ALLOC_ZERO(sizeof(_TYPEHASHENTRY*) * newsize));
|
||||||
|
|
||||||
|
if (s_typehashtable) {
|
||||||
|
for (uint32_t i = 0; i < s_typehashtablesize; i++) {
|
||||||
|
_TYPEHASHENTRY* pNext;
|
||||||
|
for (_TYPEHASHENTRY* pTable = s_typehashtable[i]; pTable; pTable = pNext) {
|
||||||
|
pNext = pTable->next;
|
||||||
|
|
||||||
|
uint32_t idx = (newsize - 1) & (pTable->type ^ pTable->subtype);
|
||||||
|
pTable->next = pNewTable[idx];
|
||||||
|
pNewTable[idx] = pTable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (s_typehashtable) {
|
||||||
|
SMemFree(s_typehashtable, __FILE__, __LINE__, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s_typehashtable = pNewTable;
|
||||||
|
s_typehashtablesize = newsize;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t idx = (s_typehashtablesize - 1) & (type ^ subtype);
|
||||||
|
|
||||||
|
_TYPEHASHENTRY* pNewTypeHash = STORM_NEW_ZERO(_TYPEHASHENTRY);
|
||||||
|
|
||||||
|
pNewTypeHash->type = type;
|
||||||
|
pNewTypeHash->subtype = subtype;
|
||||||
|
pNewTypeHash->idhashtable = STORM_NEW_ZERO(_IDHASHTABLE);
|
||||||
|
pNewTypeHash->next = s_typehashtable[idx];
|
||||||
|
|
||||||
|
s_typehashtable[idx] = pNewTypeHash;
|
||||||
|
s_typehashtableused++;
|
||||||
|
|
||||||
|
pTypeHash = pNewTypeHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pTypeHash->idhashtable->used >= pTypeHash->idhashtable->size / 2) {
|
||||||
|
uint32_t newsize = ComputeNewTableSize(pTypeHash->idhashtable->size);
|
||||||
|
_IDHASHENTRY** pNewTable = static_cast<_IDHASHENTRY**>(STORM_ALLOC_ZERO(sizeof(_IDHASHENTRY*) * newsize));
|
||||||
|
_IDHASHENTRY*** pTempTable = static_cast<_IDHASHENTRY***>(STORM_ALLOC_ZERO(sizeof(_IDHASHENTRY*) * newsize));
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < newsize; i++) {
|
||||||
|
pTempTable[i] = &pNewTable[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pTypeHash->idhashtable->data && pTypeHash->idhashtable->size != 0) {
|
||||||
|
for (uint32_t i = 0; i < pTypeHash->idhashtable->size; i++) {
|
||||||
|
_IDHASHENTRY* pNext;
|
||||||
|
for (_IDHASHENTRY* pEntry = pTypeHash->idhashtable->data[i]; pEntry; pEntry = pNext) {
|
||||||
|
uint32_t idx = (newsize - 1) & pEntry->id;
|
||||||
|
pNext = pEntry->next;
|
||||||
|
pEntry->next = nullptr;
|
||||||
|
|
||||||
|
*pTempTable[idx] = pEntry;
|
||||||
|
pTempTable[idx] = &pEntry->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SMemFree(pTempTable, __FILE__, __LINE__, 0);
|
||||||
|
if (pTypeHash->idhashtable->data) {
|
||||||
|
SMemFree(pTypeHash->idhashtable->data, __FILE__, __LINE__, 0);
|
||||||
|
}
|
||||||
|
pTypeHash->idhashtable->data = pNewTable;
|
||||||
|
pTypeHash->idhashtable->size = newsize;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t idx = (pTypeHash->idhashtable->size - 1) & id;
|
||||||
|
_IDHASHENTRY* pNewIdHash = STORM_NEW_ZERO(_IDHASHENTRY);
|
||||||
|
|
||||||
|
pNewIdHash->id = id;
|
||||||
|
pNewIdHash->sequence = ++pTypeHash->sequence;
|
||||||
|
pNewIdHash->handler = handler;
|
||||||
|
|
||||||
|
pNewIdHash->next = pTypeHash->idhashtable->data[idx];
|
||||||
|
pTypeHash->idhashtable->data[idx] = pNewIdHash;
|
||||||
|
|
||||||
|
pTypeHash->idhashtable->used++;
|
||||||
|
|
||||||
|
s_modified = 1;
|
||||||
|
s_critsect.Leave();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t SEvtUnregisterHandler(uint32_t type, uint32_t subtype, uint32_t id, SEVTHANDLER handler) {
|
||||||
|
int32_t success = 0;
|
||||||
|
s_critsect.Enter();
|
||||||
|
|
||||||
|
_TYPEHASHENTRY* pTypeEntry = FindTypeHashEntry(type, subtype);
|
||||||
|
if (pTypeEntry) {
|
||||||
|
_IDHASHTABLE* pTable = pTypeEntry->idhashtable;
|
||||||
|
if (pTable->data && pTable->size != 0) {
|
||||||
|
_IDHASHENTRY** ppNextEntry = &pTable->data[id & (pTable->size - 1)];
|
||||||
|
for (_IDHASHENTRY* pEntry = *ppNextEntry; pEntry; pEntry = *ppNextEntry) {
|
||||||
|
if (pEntry->id == id && (!handler || pEntry->handler == handler)) {
|
||||||
|
*ppNextEntry = pEntry->next;
|
||||||
|
delete pEntry;
|
||||||
|
|
||||||
|
success = 1;
|
||||||
|
s_modified = 1;
|
||||||
|
pTable->used--;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ppNextEntry = &pEntry->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s_critsect.Leave();
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t SEvtUnregisterType(uint32_t type, uint32_t subtype) {
|
||||||
|
int32_t success = 0;
|
||||||
|
s_critsect.Enter();
|
||||||
|
|
||||||
|
_TYPEHASHENTRY *pTypeEntry = FindTypeHashEntry(type, subtype);
|
||||||
|
if (pTypeEntry) {
|
||||||
|
for (auto pTable = pTypeEntry->idhashtable; pTable; pTable = pTypeEntry->idhashtable) {
|
||||||
|
pTypeEntry->idhashtable = pTable->next;
|
||||||
|
DeleteIdHashTable(pTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t idx = (s_typehashtablesize - 1) & (subtype ^ type);
|
||||||
|
_TYPEHASHENTRY** ppNextEntry = &s_typehashtable[idx];
|
||||||
|
for (_TYPEHASHENTRY* pEntry = *ppNextEntry; pEntry; pEntry = *ppNextEntry) {
|
||||||
|
if (pEntry == pTypeEntry) {
|
||||||
|
*ppNextEntry = pEntry->next;
|
||||||
|
delete pEntry;
|
||||||
|
s_typehashtableused--;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ppNextEntry = &pEntry->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
success = 1;
|
||||||
|
s_modified = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
s_critsect.Leave();
|
||||||
|
return success;
|
||||||
|
}
|
||||||
34
storm/Event.h
Normal file
34
storm/Event.h
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
#ifndef STORM_EVENT_HPP
|
||||||
|
#define STORM_EVENT_HPP
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#ifndef STORMAPI
|
||||||
|
#if defined(_MSC_VER)
|
||||||
|
#define STORMAPI __stdcall
|
||||||
|
#else
|
||||||
|
#define STORMAPI
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef void (STORMAPI* SEVTHANDLER)(void*);
|
||||||
|
|
||||||
|
|
||||||
|
int32_t SEvtBreakHandlerChain(void* data);
|
||||||
|
|
||||||
|
int32_t SEvtDestroy();
|
||||||
|
|
||||||
|
int32_t SEvtDispatch(uint32_t type, uint32_t subtype, uint32_t id, void* data);
|
||||||
|
|
||||||
|
int32_t SEvtPopState(uint32_t type, uint32_t subtype);
|
||||||
|
|
||||||
|
int32_t SEvtPushState(uint32_t type, uint32_t subtype);
|
||||||
|
|
||||||
|
int32_t SEvtRegisterHandler(uint32_t type, uint32_t subtype, uint32_t id, uint32_t flags, SEVTHANDLER handler);
|
||||||
|
|
||||||
|
int32_t SEvtUnregisterHandler(uint32_t type, uint32_t subtype, uint32_t id, SEVTHANDLER handler);
|
||||||
|
|
||||||
|
int32_t SEvtUnregisterType(uint32_t type, uint32_t subtype);
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <new>
|
||||||
|
|
||||||
#define SMEM_FLAG_ZEROMEMORY 0x8
|
#define SMEM_FLAG_ZEROMEMORY 0x8
|
||||||
|
|
||||||
|
|
|
||||||
591
test/Event.cpp
Normal file
591
test/Event.cpp
Normal file
|
|
@ -0,0 +1,591 @@
|
||||||
|
#include "EventTest.h"
|
||||||
|
|
||||||
|
|
||||||
|
static void STORMAPI TestBreakEventHandlerSelf(void* data) {
|
||||||
|
EventHandlerTest::RegisterCall(10, data);
|
||||||
|
|
||||||
|
CHECK(SEvtBreakHandlerChain(data) == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void STORMAPI TestBreakEventHandlerOther(void* data) {
|
||||||
|
EventHandlerTest::RegisterCall(10, data);
|
||||||
|
|
||||||
|
int bunk = 0;
|
||||||
|
CHECK(SEvtBreakHandlerChain(&bunk) == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("SEvtBreakHandlerChain", "[event]") {
|
||||||
|
EventHandlerTest test;
|
||||||
|
|
||||||
|
SECTION("can use nullptr as data") {
|
||||||
|
CHECK(SEvtBreakHandlerChain(nullptr) == 1);
|
||||||
|
|
||||||
|
SEvtRegisterHandler(7357, 1, 0, 0, &TestEventHandler1);
|
||||||
|
CHECK(SEvtDispatch(7357, 1, 0, nullptr) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("causes SEvtDispatch to break early if one of many data matches") {
|
||||||
|
int data1 = 42, data2 = 1337, data3 = 0xFFFFFFFF;
|
||||||
|
|
||||||
|
CHECK(SEvtBreakHandlerChain(nullptr) == 1);
|
||||||
|
CHECK(SEvtBreakHandlerChain(&data1) == 1);
|
||||||
|
CHECK(SEvtBreakHandlerChain(&data2) == 1);
|
||||||
|
CHECK(SEvtBreakHandlerChain(&data3) == 1);
|
||||||
|
|
||||||
|
SEvtRegisterHandler(7357, 1, 0, 0, &TestEventHandler1);
|
||||||
|
CHECK(SEvtDispatch(7357, 1, 0, &data2) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("doesn't break SEvtDispatch if no data matches") {
|
||||||
|
int data1 = 42, data2 = 1337, data3 = 0xFFFFFFFF;
|
||||||
|
|
||||||
|
CHECK(SEvtBreakHandlerChain(nullptr) == 1);
|
||||||
|
CHECK(SEvtBreakHandlerChain(&data1) == 1);
|
||||||
|
CHECK(SEvtBreakHandlerChain(&data2) == 1);
|
||||||
|
CHECK(SEvtBreakHandlerChain(&data3) == 1);
|
||||||
|
|
||||||
|
SEvtRegisterHandler(7357, 1, 0, 0, &TestEventHandler1);
|
||||||
|
CHECK(SEvtDispatch(7357, 1, 0, &test) == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("deduplicates multiple same-data breaks") {
|
||||||
|
CHECK(SEvtBreakHandlerChain(&test) == 1);
|
||||||
|
CHECK(SEvtBreakHandlerChain(&test) == 1);
|
||||||
|
CHECK(SEvtBreakHandlerChain(&test) == 1);
|
||||||
|
|
||||||
|
SEvtRegisterHandler(7357, 1, 0, 0, &TestEventHandler1);
|
||||||
|
CHECK(SEvtDispatch(7357, 1, 0, &test) == 0);
|
||||||
|
CHECK(SEvtDispatch(7357, 1, 0, &test) == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("causes a dispatch to stop during handling") {
|
||||||
|
SEvtRegisterHandler(0, 0, 0, 0, &TestEventHandler1);
|
||||||
|
SEvtRegisterHandler(0, 0, 0, 0, &TestBreakEventHandlerSelf);
|
||||||
|
SEvtRegisterHandler(0, 0, 0, 0, &TestEventHandler2);
|
||||||
|
|
||||||
|
CHECK(SEvtDispatch(0, 0, 0, nullptr) == 1);
|
||||||
|
CHECK(test.NumCalls() == 2);
|
||||||
|
// Calls are reverse order, TestEventHandler1 doesn't get called
|
||||||
|
CHECK_THAT(test.CallResult(), MatchesCall({ 2, nullptr }));
|
||||||
|
CHECK_THAT(test.CallResult(), MatchesCall({ 10, nullptr }));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("doesn't cause a dispatch to stop during handling if data differs") {
|
||||||
|
SEvtRegisterHandler(0, 0, 0, 0, &TestEventHandler1);
|
||||||
|
SEvtRegisterHandler(0, 0, 0, 0, &TestBreakEventHandlerOther);
|
||||||
|
SEvtRegisterHandler(0, 0, 0, 0, &TestEventHandler2);
|
||||||
|
|
||||||
|
CHECK(SEvtDispatch(0, 0, 0, nullptr) == 1);
|
||||||
|
CHECK(test.NumCalls() == 3);
|
||||||
|
// Calls are reverse order, TestEventHandler1 doesn't get called
|
||||||
|
CHECK_THAT(test.CallResult(), MatchesCall({ 2, nullptr }));
|
||||||
|
CHECK_THAT(test.CallResult(), MatchesCall({ 10, nullptr }));
|
||||||
|
CHECK_THAT(test.CallResult(), MatchesCall({ 1, nullptr }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("SEvtDestroy", "[event]") {
|
||||||
|
EventHandlerTest test;
|
||||||
|
|
||||||
|
SECTION("always returns 1") {
|
||||||
|
CHECK(SEvtDestroy() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("destroys all event handlers") {
|
||||||
|
SEvtRegisterHandler(1, 1, 1, 0, &TestEventHandler1);
|
||||||
|
CHECK(SEvtDispatch(1, 1, 1, nullptr) == 1);
|
||||||
|
CHECK(test.NumCalls() == 1);
|
||||||
|
|
||||||
|
CHECK(SEvtDispatch(1, 1, 1, nullptr) == 1);
|
||||||
|
CHECK(test.NumCalls() == 2);
|
||||||
|
|
||||||
|
CHECK(SEvtDestroy() == 1);
|
||||||
|
|
||||||
|
// Can't increment calls since the handler was destroyed
|
||||||
|
CHECK(SEvtDispatch(1, 1, 1, nullptr) == 0);
|
||||||
|
CHECK(test.NumCalls() == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("doesn't destroy break data") {
|
||||||
|
// not ideal but it's official behaviour
|
||||||
|
SEvtBreakHandlerChain(nullptr);
|
||||||
|
|
||||||
|
CHECK(SEvtDestroy() == 1);
|
||||||
|
|
||||||
|
SEvtRegisterHandler(0, 0, 0, 0, &TestEventHandler1);
|
||||||
|
CHECK(SEvtDispatch(0, 0, 0, nullptr) == 0);
|
||||||
|
CHECK(SEvtDispatch(0, 0, 0, nullptr) == 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void STORMAPI TestNestedDispatchEventHandler(void* data) {
|
||||||
|
EventHandlerTest::RegisterCall(20, data);
|
||||||
|
|
||||||
|
CHECK(SEvtDispatch(1337, 420, 69, nullptr) == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("SEvtDispatch", "[event]") {
|
||||||
|
EventHandlerTest test;
|
||||||
|
|
||||||
|
SECTION("sends data to an event handler") {
|
||||||
|
SEvtRegisterHandler(1337, 42, 5, 0, &TestEventHandler1);
|
||||||
|
|
||||||
|
int data = 5;
|
||||||
|
CHECK(SEvtDispatch(1337, 42, 5, &data) == 1);
|
||||||
|
|
||||||
|
REQUIRE(test.NumCalls() == 1);
|
||||||
|
CHECK_THAT(test.CallResult(), MatchesCall({ 1, &data }));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("can use 0 as valid handler ids") {
|
||||||
|
SEvtRegisterHandler(0, 0, 0, 0, &TestEventHandler1);
|
||||||
|
|
||||||
|
int data = 5;
|
||||||
|
CHECK(SEvtDispatch(0, 0, 0, &data) == 1);
|
||||||
|
|
||||||
|
REQUIRE(test.NumCalls() == 1);
|
||||||
|
CHECK_THAT(test.CallResult(), MatchesCall({ 1, &data }));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("can pass nullptr to a handler") {
|
||||||
|
SEvtRegisterHandler(0, 0, 0, 0, &TestEventHandler1);
|
||||||
|
|
||||||
|
CHECK(SEvtDispatch(0, 0, 0, nullptr) == 1);
|
||||||
|
|
||||||
|
REQUIRE(test.NumCalls() == 1);
|
||||||
|
CHECK_THAT(test.CallResult(), MatchesCall({ 1, nullptr }));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("sends data to multiple event handlers") {
|
||||||
|
SEvtRegisterHandler(7357, 1, 0, 0, &TestEventHandler1);
|
||||||
|
SEvtRegisterHandler(7357, 1, 0, 0, &TestEventHandler2);
|
||||||
|
SEvtRegisterHandler(7357, 1, 0, 0, &TestEventHandler3);
|
||||||
|
|
||||||
|
int data = 1;
|
||||||
|
CHECK(SEvtDispatch(7357, 1, 0, &data) == 1);
|
||||||
|
|
||||||
|
REQUIRE(test.NumCalls() == 3);
|
||||||
|
CHECK_THAT(test.CallResult(), MatchesCall({ 3, &data }));
|
||||||
|
CHECK_THAT(test.CallResult(), MatchesCall({ 2, &data }));
|
||||||
|
CHECK_THAT(test.CallResult(), MatchesCall({ 1, &data }));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("sends data to multiple event handlers with reverse ordering") {
|
||||||
|
SEvtRegisterHandler(7357, 1, 0, 0, &TestEventHandler3);
|
||||||
|
SEvtRegisterHandler(7357, 1, 0, 0, &TestEventHandler1);
|
||||||
|
SEvtRegisterHandler(7357, 1, 0, 0, &TestEventHandler2);
|
||||||
|
|
||||||
|
int data = 1;
|
||||||
|
CHECK(SEvtDispatch(7357, 1, 0, &data) == 1);
|
||||||
|
|
||||||
|
REQUIRE(test.NumCalls() == 3);
|
||||||
|
CHECK_THAT(test.CallResult(), MatchesCall({ 2, &data }));
|
||||||
|
CHECK_THAT(test.CallResult(), MatchesCall({ 1, &data }));
|
||||||
|
CHECK_THAT(test.CallResult(), MatchesCall({ 3, &data }));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("sends data to duplicate handlers") {
|
||||||
|
SEvtRegisterHandler(7357, 1, 0, 0, &TestEventHandler1);
|
||||||
|
SEvtRegisterHandler(7357, 1, 0, 0, &TestEventHandler1);
|
||||||
|
SEvtRegisterHandler(7357, 1, 0, 0, &TestEventHandler1);
|
||||||
|
|
||||||
|
int data = 1;
|
||||||
|
CHECK(SEvtDispatch(7357, 1, 0, &data) == 1);
|
||||||
|
|
||||||
|
REQUIRE(test.NumCalls() == 3);
|
||||||
|
CHECK_THAT(test.CallResult(), MatchesCall({ 1, &data }));
|
||||||
|
CHECK_THAT(test.CallResult(), MatchesCall({ 1, &data }));
|
||||||
|
CHECK_THAT(test.CallResult(), MatchesCall({ 1, &data }));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("does nothing if there are no handlers in the type") {
|
||||||
|
SEvtRegisterHandler(7357, 1, 0, 0, &TestEventHandler1);
|
||||||
|
|
||||||
|
CHECK(SEvtDispatch(42, 1, 0, nullptr) == 0);
|
||||||
|
CHECK(test.NumCalls() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("does nothing if there are no handlers that match the subtype") {
|
||||||
|
SEvtRegisterHandler(7357, 1, 0, 0, &TestEventHandler1);
|
||||||
|
|
||||||
|
CHECK(SEvtDispatch(7357, 2, 0, nullptr) == 0);
|
||||||
|
CHECK(test.NumCalls() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("does nothing if there are no handlers that match the id") {
|
||||||
|
SEvtRegisterHandler(7357, 1, 0, 0, &TestEventHandler1);
|
||||||
|
|
||||||
|
CHECK(SEvtDispatch(7357, 1, 1, nullptr) == 0);
|
||||||
|
CHECK(test.NumCalls() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("breaks the next dispatch if data matches any in break chain") {
|
||||||
|
int data = 42;
|
||||||
|
SEvtBreakHandlerChain(&data);
|
||||||
|
|
||||||
|
SEvtRegisterHandler(7357, 1, 0, 0, &TestEventHandler1);
|
||||||
|
|
||||||
|
// doesn't call because data matches the break chain
|
||||||
|
CHECK(SEvtDispatch(7357, 1, 0, &data) == 0);
|
||||||
|
CHECK(test.NumCalls() == 0);
|
||||||
|
|
||||||
|
// calls because the break already occurred
|
||||||
|
CHECK(SEvtDispatch(7357, 1, 0, &data) == 1);
|
||||||
|
CHECK(test.NumCalls() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("calls handlers if data doesn't match any in break chain") {
|
||||||
|
int data = 42;
|
||||||
|
int data2 = 1337;
|
||||||
|
SEvtBreakHandlerChain(&data);
|
||||||
|
|
||||||
|
SEvtRegisterHandler(7357, 1, 0, 0, &TestEventHandler1);
|
||||||
|
|
||||||
|
CHECK(SEvtDispatch(7357, 1, 0, &data2) == 1);
|
||||||
|
CHECK(test.NumCalls() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("finds the correct handler among many") {
|
||||||
|
SEvtRegisterHandler(9000, 0, 0, 0, &TestEventHandler1);
|
||||||
|
SEvtRegisterHandler(9000, 1, 0, 0, &TestEventHandler1);
|
||||||
|
SEvtRegisterHandler(9000, 1, 1, 0, &TestEventHandler1);
|
||||||
|
SEvtRegisterHandler(420, 0, 0, 0, &TestEventHandler1);
|
||||||
|
SEvtRegisterHandler(420, 1, 0, 0, &TestEventHandler1);
|
||||||
|
SEvtRegisterHandler(420, 1, 1, 0, &TestEventHandler2);
|
||||||
|
SEvtRegisterHandler(420, 1, 2, 0, &TestEventHandler1);
|
||||||
|
SEvtRegisterHandler(42, 0, 0, 0, &TestEventHandler1);
|
||||||
|
|
||||||
|
CHECK(SEvtDispatch(420, 1, 1, nullptr) == 1);
|
||||||
|
|
||||||
|
CHECK(test.NumCalls() == 1);
|
||||||
|
CHECK_THAT(test.CallResult(), MatchesCall({2, nullptr}));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("can use a non-pointer as data") {
|
||||||
|
SEvtRegisterHandler(0, 0, 0, 0, &TestEventHandler1);
|
||||||
|
|
||||||
|
CHECK(SEvtDispatch(0, 0, 0, reinterpret_cast<void*>(42)) == 1);
|
||||||
|
CHECK_THAT(test.CallResult(), MatchesCall({1, reinterpret_cast<void*>(42)}));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("broken handler chain still results in success if at least 1 handler called") {
|
||||||
|
SEvtRegisterHandler(0, 0, 0, 0, &TestBreakEventHandlerOther);
|
||||||
|
|
||||||
|
CHECK(SEvtDispatch(0, 0, 0, nullptr) == 1);
|
||||||
|
CHECK(test.NumCalls() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("can be called from handlers") {
|
||||||
|
SEvtRegisterHandler(0, 0, 0, 0, &TestEventHandler1);
|
||||||
|
SEvtRegisterHandler(1337, 420, 69, 0, &TestEventHandler1);
|
||||||
|
SEvtRegisterHandler(0, 0, 0, 0, &TestEventHandler2);
|
||||||
|
SEvtRegisterHandler(0, 0, 0, 0, &TestNestedDispatchEventHandler);
|
||||||
|
SEvtRegisterHandler(1337, 420, 69, 0, &TestEventHandler3);
|
||||||
|
SEvtRegisterHandler(0, 0, 0, 0, &TestEventHandler4);
|
||||||
|
|
||||||
|
CHECK(SEvtDispatch(0, 0, 0, &test) == 1);
|
||||||
|
CHECK(test.NumCalls() == 6);
|
||||||
|
CHECK_THAT(test.CallResult(), MatchesCall({ 4, &test }));
|
||||||
|
CHECK_THAT(test.CallResult(), MatchesCall({ 20, &test }));
|
||||||
|
CHECK_THAT(test.CallResult(), MatchesCall({ 3, nullptr }));
|
||||||
|
CHECK_THAT(test.CallResult(), MatchesCall({ 1, nullptr }));
|
||||||
|
CHECK_THAT(test.CallResult(), MatchesCall({ 2, &test }));
|
||||||
|
CHECK_THAT(test.CallResult(), MatchesCall({ 1, &test }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("SEvtPopState", "[event]") {
|
||||||
|
EventHandlerTest test;
|
||||||
|
|
||||||
|
SECTION("fails if there are no handlers") {
|
||||||
|
CHECK(SEvtPopState(0, 0) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("unregisters type if it is already top level") {
|
||||||
|
SEvtRegisterHandler(1337, 420, 69, 0, &TestEventHandler1);
|
||||||
|
|
||||||
|
CHECK(SEvtPopState(1337, 420) == 1);
|
||||||
|
CHECK(SEvtUnregisterType(1337, 420) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("popped state can receive callbacks again") {
|
||||||
|
SEvtRegisterHandler(1337, 420, 69, 0, &TestEventHandler1);
|
||||||
|
CHECK(SEvtPushState(1337, 420) == 1);
|
||||||
|
|
||||||
|
CHECK(SEvtDispatch(1337, 420, 69, nullptr) == 0);
|
||||||
|
CHECK(test.NumCalls() == 0);
|
||||||
|
|
||||||
|
CHECK(SEvtPopState(1337, 420) == 0);
|
||||||
|
|
||||||
|
CHECK(SEvtDispatch(1337, 420, 69, nullptr) == 1);
|
||||||
|
CHECK(test.NumCalls() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("popped state can be unregistered again") {
|
||||||
|
SEvtRegisterHandler(1337, 420, 69, 0, &TestEventHandler1);
|
||||||
|
CHECK(SEvtPushState(1337, 420) == 1);
|
||||||
|
|
||||||
|
CHECK(SEvtUnregisterHandler(1337, 420, 69, &TestEventHandler1) == 0);
|
||||||
|
CHECK(SEvtPopState(1337, 420) == 0);
|
||||||
|
CHECK(SEvtUnregisterHandler(1337, 420, 69, &TestEventHandler1) == 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("SEvtPushState", "[event]") {
|
||||||
|
EventHandlerTest test;
|
||||||
|
|
||||||
|
SECTION("fails if there are no handlers") {
|
||||||
|
CHECK(SEvtPushState(0, 0) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("fails if the type does not match any registered types") {
|
||||||
|
SEvtRegisterHandler(0, 0, 1, 0, &TestEventHandler1);
|
||||||
|
SEvtRegisterHandler(10, 0, 1, 0, &TestEventHandler1);
|
||||||
|
SEvtRegisterHandler(0, 9, 1, 0, &TestEventHandler1);
|
||||||
|
|
||||||
|
CHECK(SEvtPushState(10, 9) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("succeeds if the handler type exists") {
|
||||||
|
SEvtRegisterHandler(0, 0, 1, 0, &TestEventHandler1);
|
||||||
|
CHECK(SEvtPushState(0, 0) == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("pushed state won't receive callbacks") {
|
||||||
|
SEvtRegisterHandler(1337, 420, 69, 0, &TestEventHandler1);
|
||||||
|
CHECK(SEvtPushState(1337, 420) == 1);
|
||||||
|
|
||||||
|
CHECK(SEvtDispatch(1337, 420, 69, nullptr) == 0);
|
||||||
|
CHECK(test.NumCalls() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("pushed state applies to all ids") {
|
||||||
|
SEvtRegisterHandler(0, 0, 9, 0, &TestEventHandler1);
|
||||||
|
SEvtRegisterHandler(0, 0, 8, 0, &TestEventHandler1);
|
||||||
|
SEvtRegisterHandler(0, 0, 66, 0, &TestEventHandler1);
|
||||||
|
|
||||||
|
CHECK(SEvtPushState(0, 0) == 1);
|
||||||
|
|
||||||
|
CHECK(SEvtDispatch(0, 0, 9, nullptr) == 0);
|
||||||
|
CHECK(SEvtDispatch(0, 0, 8, nullptr) == 0);
|
||||||
|
CHECK(SEvtDispatch(0, 0, 66, nullptr) == 0);
|
||||||
|
CHECK(test.NumCalls() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("pushed state can't be unregistered") {
|
||||||
|
SEvtRegisterHandler(0, 0, 0, 0, &TestEventHandler1);
|
||||||
|
SEvtRegisterHandler(0, 0, 0, 0, &TestEventHandler2);
|
||||||
|
|
||||||
|
CHECK(SEvtPushState(0, 0) == 1);
|
||||||
|
CHECK(SEvtUnregisterHandler(0, 0, 0, &TestEventHandler1) == 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void STORMAPI TestRegisterEventHandler(void* data) {
|
||||||
|
EventHandlerTest::RegisterCall(30, data);
|
||||||
|
|
||||||
|
CHECK(SEvtRegisterHandler(0, 0, 0, 0, &TestEventHandler4) == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("SEvtRegisterHandler", "[event]") {
|
||||||
|
EventHandlerTest test;
|
||||||
|
|
||||||
|
SECTION("registers an event handler") {
|
||||||
|
CHECK(SEvtRegisterHandler(7357, 1, 2, 0, &TestEventHandler1) == 1);
|
||||||
|
// true if found and removed
|
||||||
|
CHECK(SEvtUnregisterHandler(7357, 1, 2, &TestEventHandler1) == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("registers event handlers with different types") {
|
||||||
|
CHECK(SEvtRegisterHandler(7357, 1, 2, 0, &TestEventHandler1) == 1);
|
||||||
|
CHECK(SEvtRegisterHandler(69, 1, 2, 0, &TestEventHandler1) == 1);
|
||||||
|
|
||||||
|
CHECK(SEvtUnregisterHandler(69, 1, 2, &TestEventHandler1) == 1);
|
||||||
|
CHECK(SEvtRegisterHandler(777, 1, 2, 0, &TestEventHandler1) == 1);
|
||||||
|
|
||||||
|
CHECK(SEvtUnregisterHandler(7357, 1, 2, &TestEventHandler1) == 1);
|
||||||
|
CHECK(SEvtUnregisterHandler(777, 1, 2, &TestEventHandler1) == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("adds a new handler while being processed") {
|
||||||
|
CHECK(SEvtRegisterHandler(0, 0, 0, 0, &TestEventHandler1) == 1);
|
||||||
|
CHECK(SEvtRegisterHandler(0, 0, 0, 0, &TestRegisterEventHandler) == 1);
|
||||||
|
|
||||||
|
SEvtDispatch(0, 0, 0, nullptr);
|
||||||
|
CHECK(test.NumCalls() == 2);
|
||||||
|
CHECK_THAT(test.CallResult(), MatchesCall({30}));
|
||||||
|
CHECK_THAT(test.CallResult(), MatchesCall({1}));
|
||||||
|
|
||||||
|
SEvtDispatch(0, 0, 0, nullptr);
|
||||||
|
CHECK(test.NumCalls() == 3);
|
||||||
|
CHECK_THAT(test.CallResult(), MatchesCall({4}));
|
||||||
|
CHECK_THAT(test.CallResult(), MatchesCall({30}));
|
||||||
|
CHECK_THAT(test.CallResult(), MatchesCall({1}));
|
||||||
|
|
||||||
|
SEvtDispatch(0, 0, 0, nullptr);
|
||||||
|
CHECK(test.NumCalls() == 4);
|
||||||
|
CHECK_THAT(test.CallResult(), MatchesCall({4}));
|
||||||
|
CHECK_THAT(test.CallResult(), MatchesCall({4}));
|
||||||
|
CHECK_THAT(test.CallResult(), MatchesCall({30}));
|
||||||
|
CHECK_THAT(test.CallResult(), MatchesCall({1}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void STORMAPI TestUnregisterEventHandler(void* data) {
|
||||||
|
EventHandlerTest::RegisterCall(30, data);
|
||||||
|
|
||||||
|
CHECK(SEvtUnregisterHandler(0, 0, 0, &TestUnregisterEventHandler) == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("SEvtUnregisterHandler", "[event]") {
|
||||||
|
EventHandlerTest test;
|
||||||
|
|
||||||
|
SECTION("does nothing if handler doesn't exist") {
|
||||||
|
CHECK(SEvtUnregisterHandler(7357, 2, 1, &TestEventHandler1) == 0);
|
||||||
|
CHECK(SEvtUnregisterHandler(7357, 2, 1, nullptr) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("removes multiple handler functions if handler is null") {
|
||||||
|
SEvtRegisterHandler(7357, 1, 2, 0, &TestEventHandler1);
|
||||||
|
SEvtRegisterHandler(7357, 1, 2, 0, &TestEventHandler2);
|
||||||
|
|
||||||
|
CHECK(SEvtUnregisterHandler(7357, 1, 2, nullptr) == 1);
|
||||||
|
|
||||||
|
// Make sure nothing gets called
|
||||||
|
CHECK(SEvtDispatch(7357, 1, 2, nullptr) == 0);
|
||||||
|
CHECK(test.NumCalls() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("removes a specific event handler") {
|
||||||
|
SEvtRegisterHandler(7357, 1, 2, 0, &TestEventHandler1);
|
||||||
|
SEvtRegisterHandler(7357, 1, 2, 0, &TestEventHandler2);
|
||||||
|
|
||||||
|
CHECK(SEvtUnregisterHandler(7357, 1, 2, &TestEventHandler1) == 1);
|
||||||
|
|
||||||
|
// Make sure the other handler wasn't removed by checking if it is called
|
||||||
|
CHECK(SEvtDispatch(7357, 1, 2, nullptr) == 1);
|
||||||
|
CHECK_THAT(test.CallResult(), MatchesCall({ 2, nullptr }));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("removes nothing if only type differs") {
|
||||||
|
SEvtRegisterHandler(7357, 1, 2, 0, &TestEventHandler1);
|
||||||
|
|
||||||
|
CHECK(SEvtUnregisterHandler(42, 1, 2, nullptr) == 0);
|
||||||
|
|
||||||
|
CHECK(SEvtDispatch(7357, 1, 2, nullptr) == 1);
|
||||||
|
CHECK(test.NumCalls() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("removes nothing if only subtype differs") {
|
||||||
|
SEvtRegisterHandler(7357, 1, 2, 0, &TestEventHandler1);
|
||||||
|
|
||||||
|
CHECK(SEvtUnregisterHandler(7357, 2, 2, nullptr) == 0);
|
||||||
|
|
||||||
|
CHECK(SEvtDispatch(7357, 1, 2, nullptr) == 1);
|
||||||
|
CHECK(test.NumCalls() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("removes nothing if only id differs") {
|
||||||
|
SEvtRegisterHandler(7357, 1, 2, 0, &TestEventHandler1);
|
||||||
|
|
||||||
|
CHECK(SEvtUnregisterHandler(7357, 1, 3, nullptr) == 0);
|
||||||
|
|
||||||
|
CHECK(SEvtDispatch(7357, 1, 2, nullptr) == 1);
|
||||||
|
CHECK(test.NumCalls() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("allows a handler to remove itself while being called") {
|
||||||
|
SEvtRegisterHandler(0, 0, 0, 0, &TestUnregisterEventHandler);
|
||||||
|
|
||||||
|
CHECK(SEvtDispatch(0, 0, 0, nullptr) == 1);
|
||||||
|
CHECK(test.NumCalls() == 1);
|
||||||
|
|
||||||
|
CHECK(SEvtDispatch(0, 0, 0, nullptr) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("stops calls once a handler removes itself") {
|
||||||
|
SEvtRegisterHandler(0, 0, 0, 0, &TestUnregisterEventHandler);
|
||||||
|
SEvtRegisterHandler(0, 0, 0, 0, &TestUnregisterEventHandler);
|
||||||
|
SEvtRegisterHandler(0, 0, 0, 0, &TestUnregisterEventHandler);
|
||||||
|
|
||||||
|
CHECK(SEvtDispatch(0, 0, 0, nullptr) == 1);
|
||||||
|
CHECK(test.NumCalls() == 1);
|
||||||
|
CHECK(SEvtDispatch(0, 0, 0, nullptr) == 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void STORMAPI TestUnregisterTypeHandler(void* data) {
|
||||||
|
EventHandlerTest::RegisterCall(30, data);
|
||||||
|
|
||||||
|
CHECK(SEvtUnregisterType(0, 0) == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("SEvtUnregisterType", "[event]") {
|
||||||
|
EventHandlerTest test;
|
||||||
|
|
||||||
|
SECTION("removes all event handlers in matching type and subtype") {
|
||||||
|
SEvtRegisterHandler(7357, 1, 2, 0, &TestEventHandler1);
|
||||||
|
SEvtRegisterHandler(7357, 1, 2, 0, &TestEventHandler2);
|
||||||
|
|
||||||
|
CHECK(SEvtUnregisterType(7357, 1) == 1);
|
||||||
|
|
||||||
|
CHECK(SEvtDispatch(7357, 1, 2, nullptr) == 0);
|
||||||
|
CHECK(test.NumCalls() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("removes nothing if only type matches") {
|
||||||
|
SEvtRegisterHandler(7357, 2, 2, 0, &TestEventHandler1);
|
||||||
|
|
||||||
|
CHECK(SEvtUnregisterType(7357, 1) == 0);
|
||||||
|
|
||||||
|
CHECK(SEvtDispatch(7357, 2, 2, nullptr) == 1);
|
||||||
|
CHECK(test.NumCalls() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("removes nothing if only subtype matches") {
|
||||||
|
SEvtRegisterHandler(7357, 1, 2, 0, &TestEventHandler1);
|
||||||
|
|
||||||
|
CHECK(SEvtUnregisterType(42, 1) == 0);
|
||||||
|
|
||||||
|
CHECK(SEvtDispatch(7357, 1, 2, nullptr) == 1);
|
||||||
|
CHECK(test.NumCalls() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("removes only matching handlers among many") {
|
||||||
|
SEvtRegisterHandler(123, 1, 2, 0, &TestEventHandler1);
|
||||||
|
SEvtRegisterHandler(123, 2, 2, 0, &TestEventHandler2);
|
||||||
|
SEvtRegisterHandler(123, 2, 3, 0, &TestEventHandler3);
|
||||||
|
SEvtRegisterHandler(123, 1, 1, 0, &TestEventHandler4);
|
||||||
|
SEvtRegisterHandler(123, 2, 1, 0, &TestEventHandler1);
|
||||||
|
|
||||||
|
CHECK(SEvtUnregisterType(123, 2) == 1);
|
||||||
|
|
||||||
|
CHECK(SEvtDispatch(123, 2, 1, nullptr) == 0);
|
||||||
|
CHECK(SEvtDispatch(123, 2, 2, nullptr) == 0);
|
||||||
|
CHECK(SEvtDispatch(123, 2, 3, nullptr) == 0);
|
||||||
|
|
||||||
|
CHECK(SEvtDispatch(123, 1, 1, nullptr) == 1);
|
||||||
|
CHECK(test.NumCalls() == 1);
|
||||||
|
CHECK(SEvtDispatch(123, 1, 2, nullptr) == 1);
|
||||||
|
CHECK(test.NumCalls() == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("allows a handler to remove itself while being called") {
|
||||||
|
SEvtRegisterHandler(0, 0, 0, 0, &TestUnregisterTypeHandler);
|
||||||
|
|
||||||
|
CHECK(SEvtDispatch(0, 0, 0, nullptr) == 1);
|
||||||
|
CHECK(test.NumCalls() == 1);
|
||||||
|
|
||||||
|
CHECK(SEvtDispatch(0, 0, 0, nullptr) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("stops calls once a handler removes itself") {
|
||||||
|
SEvtRegisterHandler(0, 0, 0, 0, &TestUnregisterTypeHandler);
|
||||||
|
SEvtRegisterHandler(0, 0, 0, 0, &TestUnregisterTypeHandler);
|
||||||
|
SEvtRegisterHandler(0, 0, 0, 0, &TestUnregisterTypeHandler);
|
||||||
|
|
||||||
|
CHECK(SEvtDispatch(0, 0, 0, nullptr) == 1);
|
||||||
|
CHECK(test.NumCalls() == 1);
|
||||||
|
CHECK(SEvtDispatch(0, 0, 0, nullptr) == 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
90
test/EventTest.h
Normal file
90
test/EventTest.h
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
#include "test/Test.hpp"
|
||||||
|
#include "storm/Event.h"
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
|
||||||
|
void SEvtCleanExtraDataForTests();
|
||||||
|
|
||||||
|
|
||||||
|
struct EventHandlerCalledWith {
|
||||||
|
int handler;
|
||||||
|
void* data;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::deque<EventHandlerCalledWith> EventHandlerCallResults;
|
||||||
|
|
||||||
|
struct EventHandlerTest {
|
||||||
|
EventHandlerTest() {
|
||||||
|
EventHandlerCallResults.clear();
|
||||||
|
SEvtDestroy();
|
||||||
|
SEvtCleanExtraDataForTests();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void RegisterCall(int handler, void* data) {
|
||||||
|
EventHandlerCalledWith calledWith = {handler, data};
|
||||||
|
EventHandlerCallResults.push_back(calledWith);
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t NumCalls() {
|
||||||
|
return EventHandlerCallResults.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
static EventHandlerCalledWith CallResult() {
|
||||||
|
if (EventHandlerCallResults.empty()) {
|
||||||
|
return {-1, nullptr};
|
||||||
|
}
|
||||||
|
|
||||||
|
EventHandlerCalledWith result = EventHandlerCallResults.front();
|
||||||
|
EventHandlerCallResults.pop_front();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static void STORMAPI TestEventHandler1(void* data) {
|
||||||
|
EventHandlerTest::RegisterCall(1, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void STORMAPI TestEventHandler2(void* data) {
|
||||||
|
EventHandlerTest::RegisterCall(2, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void STORMAPI TestEventHandler3(void* data) {
|
||||||
|
EventHandlerTest::RegisterCall(3, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void STORMAPI TestEventHandler4(void* data) {
|
||||||
|
EventHandlerTest::RegisterCall(4, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Helpers for comparing EventHandlerCalledWith structs
|
||||||
|
std::ostream& operator <<(std::ostream& os, EventHandlerCalledWith const& value) {
|
||||||
|
os << "{ TestEventHandler" << value.handler << ", " << value.data << " }";
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
class EventHandlerCalledWithMatcher : public Catch::MatcherBase<T> {
|
||||||
|
private:
|
||||||
|
T cmp;
|
||||||
|
|
||||||
|
public:
|
||||||
|
EventHandlerCalledWithMatcher(T arg) : cmp(arg) {}
|
||||||
|
|
||||||
|
bool match(T const& in) const override {
|
||||||
|
return cmp.handler == in.handler && cmp.data == in.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string describe() const override {
|
||||||
|
std::ostringstream ss;
|
||||||
|
ss << "equals " << cmp;
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
EventHandlerCalledWithMatcher<EventHandlerCalledWith> MatchesCall(EventHandlerCalledWith arg) {
|
||||||
|
return { arg };
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue