TONY: Further conversion of initially launched threads to processes.
This includes all the dependent routines that they call.
This commit is contained in:
parent
20a47ff7c9
commit
770e55d065
11 changed files with 284 additions and 200 deletions
|
@ -49,6 +49,7 @@
|
|||
#define TONY_ADV_H
|
||||
|
||||
#include "tony/mpal/memory.h"
|
||||
#include "tony/coroutine.h"
|
||||
#include "tony/gfxcore.h"
|
||||
|
||||
|
||||
|
@ -91,7 +92,7 @@ enum RMTonyAction {
|
|||
|
||||
// Funzioni globali
|
||||
HANDLE MainLoadLocation(int nLoc, RMPoint pt, RMPoint start);
|
||||
HANDLE MainUnloadLocation(bool bDoOnExit);
|
||||
void MainUnloadLocation(CORO_PARAM, bool bDoOnExit, HANDLE *result);
|
||||
void MainLinkGraphicTask(RMGfxTask *task);
|
||||
void MainFreeze(void);
|
||||
void MainUnfreeze(void);
|
||||
|
|
|
@ -72,7 +72,7 @@ RMInventory *Inventory;
|
|||
RMInput *Input;
|
||||
|
||||
HANDLE (*LoadLocation)(int, RMPoint, RMPoint start);
|
||||
HANDLE (*UnloadLocation)(bool bDoOnExit);
|
||||
void (*UnloadLocation)(CORO_PARAM, bool bDoOnExit, HANDLE *result);
|
||||
void (*LinkGraphicTask)(RMGfxTask *task);
|
||||
void (*Freeze)(void);
|
||||
void (*Unfreeze)(void);
|
||||
|
@ -505,20 +505,27 @@ RMPoint SFM_pt;
|
|||
int SFM_nLoc;
|
||||
|
||||
DECLARE_CUSTOM_FUNCTION(SendFullscreenMsgStart)(CORO_PARAM, uint32 nMsg, uint32 nFont, uint32, uint32) {
|
||||
RMMessage msg(nMsg);
|
||||
CORO_BEGIN_CONTEXT;
|
||||
RMMessage *msg;
|
||||
RMGfxClearTask clear;
|
||||
int i;
|
||||
CORO_END_CONTEXT(_ctx);
|
||||
|
||||
CORO_BEGIN_CODE(_ctx);
|
||||
|
||||
_ctx->msg = new RMMessage(nMsg);
|
||||
|
||||
SFM_nLoc = Loc->TEMPGetNumLoc();
|
||||
SFM_pt = Tony->Position();
|
||||
|
||||
if (bSkipIdle) return;
|
||||
if (bSkipIdle)
|
||||
return;
|
||||
|
||||
UnloadLocation(false);
|
||||
CORO_INVOKE_2(UnloadLocation, false, NULL);
|
||||
Tony->Hide();
|
||||
Unfreeze();
|
||||
|
||||
for (i = 0; i < msg.NumPeriods() && !bSkipIdle; i++) {
|
||||
for (_ctx->i = 0; _ctx->i < _ctx->msg->NumPeriods() && !bSkipIdle; _ctx->i++) {
|
||||
RMTextDialog text;
|
||||
|
||||
text.SetInput(Input);
|
||||
|
@ -534,9 +541,9 @@ DECLARE_CUSTOM_FUNCTION(SendFullscreenMsgStart)(CORO_PARAM, uint32 nMsg, uint32
|
|||
|
||||
// Scrive il testo
|
||||
if (nFont == 0)
|
||||
text.WriteText(msg[i],1);
|
||||
text.WriteText((*_ctx->msg)[_ctx->i], 1);
|
||||
else if (nFont == 1)
|
||||
text.WriteText(msg[i],0);
|
||||
text.WriteText((*_ctx->msg)[_ctx->i], 0);
|
||||
|
||||
// Setta la posizione
|
||||
text.SetPosition(RMPoint(320, 240));
|
||||
|
@ -545,13 +552,17 @@ DECLARE_CUSTOM_FUNCTION(SendFullscreenMsgStart)(CORO_PARAM, uint32 nMsg, uint32
|
|||
text.ForceTime();
|
||||
|
||||
// Registra il testo
|
||||
LinkGraphicTask(&clear);
|
||||
LinkGraphicTask(&_ctx->clear);
|
||||
LinkGraphicTask(&text);
|
||||
|
||||
// Aspetta la fine della visualizzazione
|
||||
text.SetCustomSkipHandle(hSkipIdle);
|
||||
text.WaitForEndDisplay();
|
||||
}
|
||||
|
||||
delete _ctx->msg;
|
||||
|
||||
CORO_END_CODE;
|
||||
}
|
||||
|
||||
DECLARE_CUSTOM_FUNCTION(ClearScreen)(CORO_PARAM, uint32, uint32, uint32, uint32) {
|
||||
|
@ -592,6 +603,11 @@ DECLARE_CUSTOM_FUNCTION(NoOcchioDiBue)(CORO_PARAM, uint32, uint32, uint32, uint3
|
|||
}
|
||||
|
||||
DECLARE_CUSTOM_FUNCTION(CloseLocation)(CORO_PARAM, uint32, uint32, uint32, uint32) {
|
||||
CORO_BEGIN_CONTEXT;
|
||||
CORO_END_CONTEXT(_ctx);
|
||||
|
||||
CORO_BEGIN_CODE(_ctx);
|
||||
|
||||
if (!bNoOcchioDiBue) {
|
||||
InitWipe(1);
|
||||
WaitWipeEnd();
|
||||
|
@ -600,13 +616,19 @@ DECLARE_CUSTOM_FUNCTION(CloseLocation)(CORO_PARAM, uint32, uint32, uint32, uint3
|
|||
_vm->StopMusic(4);
|
||||
|
||||
// On Exit e lascia freezzato
|
||||
UnloadLocation(true);
|
||||
CORO_INVOKE_2(UnloadLocation, true, NULL);
|
||||
Unfreeze();
|
||||
|
||||
CORO_END_CODE;
|
||||
}
|
||||
|
||||
|
||||
DECLARE_CUSTOM_FUNCTION(ChangeLocation)(CORO_PARAM, uint32 nLoc, uint32 tX, uint32 tY, uint32 bUseStartPos) {
|
||||
CORO_BEGIN_CONTEXT;
|
||||
HANDLE h;
|
||||
CORO_END_CONTEXT(_ctx);
|
||||
|
||||
CORO_BEGIN_CODE(_ctx);
|
||||
|
||||
if (!bNoOcchioDiBue) {
|
||||
InitWipe(1);
|
||||
|
@ -618,7 +640,7 @@ DECLARE_CUSTOM_FUNCTION(ChangeLocation)(CORO_PARAM, uint32 nLoc, uint32 tX, uint
|
|||
}
|
||||
|
||||
// On Exit e lascia freezzato
|
||||
UnloadLocation(true);
|
||||
CORO_INVOKE_2(UnloadLocation, true, NULL);
|
||||
|
||||
curChangedHotspot = 0;
|
||||
if (bUseStartPos != 0)
|
||||
|
@ -639,7 +661,7 @@ DECLARE_CUSTOM_FUNCTION(ChangeLocation)(CORO_PARAM, uint32 nLoc, uint32 tX, uint
|
|||
Unfreeze();
|
||||
|
||||
|
||||
h = mpalQueryDoAction(0, nLoc,0);
|
||||
_ctx->h = mpalQueryDoAction(0, nLoc, 0);
|
||||
|
||||
if (!bNoOcchioDiBue) {
|
||||
WaitWipeEnd();
|
||||
|
@ -649,9 +671,10 @@ DECLARE_CUSTOM_FUNCTION(ChangeLocation)(CORO_PARAM, uint32 nLoc, uint32 tX, uint
|
|||
bNoOcchioDiBue = false;
|
||||
|
||||
// On Enter?
|
||||
if (h != INVALID_HANDLE_VALUE)
|
||||
WaitForSingleObject(h,INFINITE);
|
||||
if (_ctx->h != INVALID_HANDLE_VALUE)
|
||||
WaitForSingleObject(_ctx->h, INFINITE);
|
||||
|
||||
CORO_END_CODE;
|
||||
}
|
||||
|
||||
DECLARE_CUSTOM_FUNCTION(SetLocStartPosition)(CORO_PARAM, uint32 nLoc, uint32 lX, uint32 lY, uint32) {
|
||||
|
|
|
@ -96,8 +96,8 @@ HANDLE MainLoadLocation(int nLoc, RMPoint pt, RMPoint start) {
|
|||
return _vm->GetEngine()->LoadLocation(nLoc, pt,start);
|
||||
}
|
||||
|
||||
HANDLE MainUnloadLocation(bool bDoOnExit) {
|
||||
return _vm->GetEngine()->UnloadLocation(bDoOnExit);
|
||||
void MainUnloadLocation(CORO_PARAM, bool bDoOnExit, HANDLE *result) {
|
||||
_vm->GetEngine()->UnloadLocation(coroParam, bDoOnExit, result);
|
||||
}
|
||||
|
||||
void MainLinkGraphicTask(RMGfxTask *task) {
|
||||
|
|
|
@ -65,13 +65,21 @@ extern bool bSkipSfxNoLoop;
|
|||
|
||||
bool bIdleExited;
|
||||
|
||||
void ExitAllIdles(int nCurLoc) {
|
||||
void ExitAllIdles(CORO_PARAM, int nCurLoc) {
|
||||
CORO_BEGIN_CONTEXT;
|
||||
CORO_END_CONTEXT(_ctx);
|
||||
|
||||
CORO_BEGIN_CODE(_ctx);
|
||||
|
||||
// Chiude le idle
|
||||
bSkipSfxNoLoop = true;
|
||||
mpalEndIdlePoll(nCurLoc);
|
||||
|
||||
CORO_INVOKE_2(mpalEndIdlePoll, nCurLoc, NULL);
|
||||
|
||||
bIdleExited = true;
|
||||
bSkipSfxNoLoop = false;
|
||||
ExitThread(0);
|
||||
|
||||
CORO_END_CODE;
|
||||
}
|
||||
|
||||
RMGfxEngine::RMGfxEngine() {
|
||||
|
@ -514,17 +522,21 @@ HANDLE RMGfxEngine::LoadLocation(int nLoc, RMPoint ptTonyStart, RMPoint start) {
|
|||
return INVALID_HANDLE_VALUE; //mpalQueryDoAction(0,m_nCurLoc,0);
|
||||
}
|
||||
|
||||
HANDLE RMGfxEngine::UnloadLocation(bool bDoOnExit) {
|
||||
void RMGfxEngine::UnloadLocation(CORO_PARAM, bool bDoOnExit, HANDLE *result) {
|
||||
CORO_BEGIN_CONTEXT;
|
||||
HANDLE h;
|
||||
CORO_END_CONTEXT(_ctx);
|
||||
|
||||
CORO_BEGIN_CODE(_ctx);
|
||||
|
||||
// Scarica tutta la memoria della locazione
|
||||
mpalEndIdlePoll(m_nCurLoc);
|
||||
CORO_INVOKE_2(mpalEndIdlePoll, m_nCurLoc, NULL);
|
||||
|
||||
// On Exit?
|
||||
if (bDoOnExit) {
|
||||
h = mpalQueryDoAction(1, m_nCurLoc, 0);
|
||||
if (h != INVALID_HANDLE_VALUE)
|
||||
WaitForSingleObject(h, INFINITE);
|
||||
_ctx->h = mpalQueryDoAction(1, m_nCurLoc, 0);
|
||||
if (_ctx->h != INVALID_HANDLE_VALUE)
|
||||
WaitForSingleObject(_ctx->h, INFINITE);
|
||||
}
|
||||
|
||||
MainFreeze();
|
||||
|
@ -534,7 +546,10 @@ HANDLE RMGfxEngine::UnloadLocation(bool bDoOnExit) {
|
|||
m_bigBuf.ClearOT();
|
||||
m_loc.Unload();
|
||||
|
||||
return INVALID_HANDLE_VALUE;
|
||||
if (result != NULL)
|
||||
*result = INVALID_HANDLE_VALUE;
|
||||
|
||||
CORO_END_CODE;
|
||||
}
|
||||
|
||||
void RMGfxEngine::Init(/*HINSTANCE hInst*/) {
|
||||
|
@ -930,7 +945,7 @@ void RMGfxEngine::LoadState(const char *fn) {
|
|||
|
||||
delete f;
|
||||
|
||||
UnloadLocation(false);
|
||||
UnloadLocation(nullContext, false, NULL);
|
||||
LoadLocation(loc,tp,RMPoint(-1, -1));
|
||||
m_tony.SetPattern(RMTony::PAT_STANDRIGHT);
|
||||
MainUnfreeze();
|
||||
|
|
|
@ -138,7 +138,7 @@ public:
|
|||
|
||||
// Manage a location
|
||||
HANDLE LoadLocation(int nLoc, RMPoint ptTonyStart, RMPoint start);
|
||||
HANDLE UnloadLocation(bool bDoOnExit=true);
|
||||
void UnloadLocation(CORO_PARAM, bool bDoOnExit, HANDLE *result);
|
||||
|
||||
// Freeze and unfreeze
|
||||
void Freeze(void);
|
||||
|
|
|
@ -138,7 +138,7 @@ bool bExecutingDialog;
|
|||
|
||||
uint32 nPollingLocations[MAXPOLLINGLOCATIONS];
|
||||
HANDLE hEndPollingLocations[MAXPOLLINGLOCATIONS];
|
||||
HANDLE PollingThreads[MAXPOLLINGLOCATIONS];
|
||||
uint32 PollingThreads[MAXPOLLINGLOCATIONS];
|
||||
|
||||
HANDLE hAskChoice;
|
||||
HANDLE hDoneChoice;
|
||||
|
@ -996,7 +996,7 @@ void ShutUpActionThread(CORO_PARAM, const void *param) {
|
|||
CORO_BEGIN_CONTEXT;
|
||||
CORO_END_CONTEXT(_ctx);
|
||||
|
||||
int pid = *(const int *)param;
|
||||
uint32 pid = *(const uint32 *)param;
|
||||
|
||||
CORO_BEGIN_CODE(_ctx);
|
||||
|
||||
|
@ -1021,17 +1021,7 @@ void ShutUpActionThread(CORO_PARAM, const void *param) {
|
|||
*
|
||||
\****************************************************************************/
|
||||
|
||||
void PASCAL LocationPollThread(uint32 id) {
|
||||
uint32 *il;
|
||||
int i,j,k;
|
||||
int numitems;
|
||||
int nRealItems;
|
||||
LPMPALITEM curItem,newItem;
|
||||
int nIdleActions;
|
||||
uint32 curTime;
|
||||
uint32 dwSleepTime;
|
||||
uint32 dwId;
|
||||
|
||||
void LocationPollThread(CORO_PARAM, const void *param) {
|
||||
typedef struct {
|
||||
uint32 nItem, nAction;
|
||||
|
||||
|
@ -1046,59 +1036,77 @@ void PASCAL LocationPollThread(uint32 id) {
|
|||
|
||||
typedef struct {
|
||||
uint32 nItem;
|
||||
HANDLE hThread;
|
||||
uint32 hThread;
|
||||
} MYTHREAD;
|
||||
|
||||
CORO_BEGIN_CONTEXT;
|
||||
uint32 *il;
|
||||
int i, j, k;
|
||||
int numitems;
|
||||
int nRealItems;
|
||||
LPMPALITEM curItem,newItem;
|
||||
int nIdleActions;
|
||||
uint32 curTime;
|
||||
uint32 dwSleepTime;
|
||||
uint32 dwId;
|
||||
int ord;
|
||||
bool delayExpired;
|
||||
|
||||
MYACTION *MyActions;
|
||||
MYTHREAD *MyThreads;
|
||||
CORO_END_CONTEXT(_ctx);
|
||||
|
||||
uint32 id = *((const uint32 *)param);
|
||||
|
||||
CORO_BEGIN_CODE(_ctx);
|
||||
|
||||
/* Tanto per cominciare, e' necessario richiedere la lista degli item
|
||||
presenti nella locazione. */
|
||||
il = mpalQueryItemList(nPollingLocations[id]);
|
||||
_ctx->il = mpalQueryItemList(nPollingLocations[id]);
|
||||
|
||||
/* Contiamo gli items */
|
||||
for (numitems = 0; il[numitems] != 0; numitems++)
|
||||
for (_ctx->numitems = 0; _ctx->il[_ctx->numitems] != 0; _ctx->numitems++)
|
||||
;
|
||||
|
||||
/* Cerchiamo gli items della locazione senza idle actions e li eliminiamo
|
||||
dalla lista */
|
||||
LockItems();
|
||||
nIdleActions = 0;
|
||||
nRealItems = 0;
|
||||
for (i = 0; i < numitems; i++) {
|
||||
int ord = itemGetOrderFromNum(il[i]);
|
||||
_ctx->nIdleActions = 0;
|
||||
_ctx->nRealItems = 0;
|
||||
for (_ctx->i = 0; _ctx->i < _ctx->numitems; _ctx->i++) {
|
||||
_ctx->ord = itemGetOrderFromNum(_ctx->il[_ctx->i]);
|
||||
|
||||
if (ord == -1) continue;
|
||||
if (_ctx->ord == -1) continue;
|
||||
|
||||
curItem = lpmiItems + ord;
|
||||
_ctx->curItem = lpmiItems + _ctx->ord;
|
||||
|
||||
k = 0;
|
||||
for (j = 0; j < curItem->nActions; j++)
|
||||
if (curItem->Action[j].num == 0xFF)
|
||||
k++;
|
||||
_ctx->k = 0;
|
||||
for (_ctx->j = 0; _ctx->j < _ctx->curItem->nActions; _ctx->j++)
|
||||
if (_ctx->curItem->Action[_ctx->j].num == 0xFF)
|
||||
_ctx->k++;
|
||||
|
||||
nIdleActions += k;
|
||||
_ctx->nIdleActions += _ctx->k;
|
||||
|
||||
if (k == 0)
|
||||
if (_ctx->k == 0)
|
||||
/* Possiamo eliminare questo item dalla lista */
|
||||
il[i] = (uint32)NULL;
|
||||
_ctx->il[_ctx->i] = (uint32)NULL;
|
||||
else
|
||||
nRealItems++;
|
||||
_ctx->nRealItems++;
|
||||
}
|
||||
UnlockItems();
|
||||
|
||||
/* Se non e' rimasto nessuno possiamo uscire */
|
||||
if (nRealItems == 0) {
|
||||
GlobalFree(il);
|
||||
ExitThread(0);
|
||||
// _endthread();
|
||||
if (_ctx->nRealItems == 0) {
|
||||
GlobalFree(_ctx->il);
|
||||
CORO_KILL_SELF();
|
||||
return;
|
||||
}
|
||||
|
||||
MyThreads=(MYTHREAD *)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, nRealItems * sizeof(MYTHREAD));
|
||||
if (MyThreads == NULL) {
|
||||
GlobalFree(il);
|
||||
ExitThread(0);
|
||||
// _endthread();
|
||||
_ctx->MyThreads = (MYTHREAD *)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, _ctx->nRealItems * sizeof(MYTHREAD));
|
||||
if (_ctx->MyThreads == NULL) {
|
||||
GlobalFree(_ctx->il);
|
||||
CORO_KILL_SELF();
|
||||
return;
|
||||
}
|
||||
|
||||
/* Inizializziamo le routine random */
|
||||
|
@ -1108,142 +1116,147 @@ void PASCAL LocationPollThread(uint32 id) {
|
|||
|
||||
/* Abbiamo appurato che esiste almeno un item che contiene idle actions.
|
||||
Ora creaiamo le copie speculari delle idle actions */
|
||||
MyActions = (MYACTION *)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, nIdleActions * sizeof(MYACTION));
|
||||
if (MyActions == NULL) {
|
||||
GlobalFree(MyThreads);
|
||||
GlobalFree(il);
|
||||
ExitThread(0);
|
||||
// _endthread();
|
||||
_ctx->MyActions = (MYACTION *)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, _ctx->nIdleActions * sizeof(MYACTION));
|
||||
if (_ctx->MyActions == NULL) {
|
||||
GlobalFree(_ctx->MyThreads);
|
||||
GlobalFree(_ctx->il);
|
||||
CORO_KILL_SELF();
|
||||
return;
|
||||
}
|
||||
|
||||
LockItems();
|
||||
k = 0;
|
||||
_ctx->k = 0;
|
||||
|
||||
for (i = 0; i < numitems; i++) {
|
||||
if (il[i] == 0)
|
||||
for (_ctx->i = 0; _ctx->i < _ctx->numitems; _ctx->i++) {
|
||||
if (_ctx->il[_ctx->i] == 0)
|
||||
continue;
|
||||
|
||||
curItem = lpmiItems + itemGetOrderFromNum(il[i]);
|
||||
_ctx->curItem = lpmiItems + itemGetOrderFromNum(_ctx->il[_ctx->i]);
|
||||
|
||||
for (j = 0; j < curItem->nActions; j++)
|
||||
if (curItem->Action[j].num == 0xFF) {
|
||||
MyActions[k].nItem = il[i];
|
||||
MyActions[k].nAction = j;
|
||||
for (_ctx->j = 0; _ctx->j < _ctx->curItem->nActions; _ctx->j++)
|
||||
if (_ctx->curItem->Action[_ctx->j].num == 0xFF) {
|
||||
_ctx->MyActions[_ctx->k].nItem = _ctx->il[_ctx->i];
|
||||
_ctx->MyActions[_ctx->k].nAction = _ctx->j;
|
||||
|
||||
MyActions[k].wTime = curItem->Action[j].wTime;
|
||||
MyActions[k].perc = curItem->Action[j].perc;
|
||||
MyActions[k].when = curItem->Action[j].when;
|
||||
MyActions[k].nCmds = curItem->Action[j].nCmds;
|
||||
CopyMemory(MyActions[k].CmdNum, curItem->Action[j].CmdNum,
|
||||
_ctx->MyActions[_ctx->k].wTime = _ctx->curItem->Action[_ctx->j].wTime;
|
||||
_ctx->MyActions[_ctx->k].perc = _ctx->curItem->Action[_ctx->j].perc;
|
||||
_ctx->MyActions[_ctx->k].when = _ctx->curItem->Action[_ctx->j].when;
|
||||
_ctx->MyActions[_ctx->k].nCmds = _ctx->curItem->Action[_ctx->j].nCmds;
|
||||
CopyMemory(_ctx->MyActions[_ctx->k].CmdNum, _ctx->curItem->Action[_ctx->j].CmdNum,
|
||||
MAX_COMMANDS_PER_ACTION * sizeof(uint16));
|
||||
|
||||
MyActions[k].dwLastTime = timeGetTime();
|
||||
k++;
|
||||
_ctx->MyActions[_ctx->k].dwLastTime = timeGetTime();
|
||||
_ctx->k++;
|
||||
}
|
||||
}
|
||||
|
||||
UnlockItems();
|
||||
|
||||
/* La item list non ci serve piu' */
|
||||
GlobalFree(il);
|
||||
GlobalFree(_ctx->il);
|
||||
|
||||
|
||||
/* Eccoci al ciclo principale. */
|
||||
while (1) {
|
||||
/* Cerchiamo tra tutte le idle actions quella a cui manca meno tempo per
|
||||
l'esecuzione */
|
||||
curTime = timeGetTime();
|
||||
dwSleepTime=(uint32) - 1L;
|
||||
_ctx->curTime = timeGetTime();
|
||||
_ctx->dwSleepTime = (uint32)-1L;
|
||||
|
||||
for (k = 0;k<nIdleActions;k++)
|
||||
if (curTime >= MyActions[k].dwLastTime + MyActions[k].wTime) {
|
||||
dwSleepTime = 0;
|
||||
for (_ctx->k = 0;_ctx->k<_ctx->nIdleActions;_ctx->k++)
|
||||
if (_ctx->curTime >= _ctx->MyActions[_ctx->k].dwLastTime + _ctx->MyActions[_ctx->k].wTime) {
|
||||
_ctx->dwSleepTime = 0;
|
||||
break;
|
||||
} else
|
||||
dwSleepTime = MIN(dwSleepTime,MyActions[k].dwLastTime+MyActions[k].wTime-curTime);
|
||||
_ctx->dwSleepTime = MIN(_ctx->dwSleepTime, _ctx->MyActions[_ctx->k].dwLastTime + _ctx->MyActions[_ctx->k].wTime - _ctx->curTime);
|
||||
|
||||
/* Ci addormentiamo, ma controllando sempre l'evento che viene settato
|
||||
quando viene richiesta la nostra chiusura */
|
||||
k = WaitForSingleObject(hEndPollingLocations[id], dwSleepTime);
|
||||
if (k == WAIT_OBJECT_0)
|
||||
_ctx->k = WaitForSingleObject(hEndPollingLocations[id], _ctx->dwSleepTime);
|
||||
if (_ctx->k == WAIT_OBJECT_0)
|
||||
break;
|
||||
|
||||
for (i = 0; i < nRealItems; i++)
|
||||
if (MyThreads[i].nItem != 0) {
|
||||
if (WaitForSingleObject(MyThreads[i].hThread, 0) == WAIT_OBJECT_0)
|
||||
MyThreads[i].nItem = 0;
|
||||
for (_ctx->i = 0; _ctx->i < _ctx->nRealItems; _ctx->i++)
|
||||
if (_ctx->MyThreads[_ctx->i].nItem != 0) {
|
||||
CORO_INVOKE_3(_vm->_scheduler.waitForSingleObject, _ctx->MyThreads[_ctx->i].hThread, 0, &_ctx->delayExpired);
|
||||
|
||||
// if result ) == WAIT_OBJECT_0)
|
||||
if (!_ctx->delayExpired)
|
||||
_ctx->MyThreads[_ctx->i].nItem = 0;
|
||||
}
|
||||
|
||||
curTime = timeGetTime();
|
||||
_ctx->curTime = timeGetTime();
|
||||
|
||||
/* Cerchiamo all'interno delle idle actions quale e' necessario eseguire */
|
||||
for (k = 0; k < nIdleActions; k++)
|
||||
if (curTime >= MyActions[k].dwLastTime + MyActions[k].wTime) {
|
||||
MyActions[k].dwLastTime += MyActions[k].wTime;
|
||||
for (_ctx->k = 0; _ctx->k < _ctx->nIdleActions; _ctx->k++)
|
||||
if (_ctx->curTime >= _ctx->MyActions[_ctx->k].dwLastTime + _ctx->MyActions[_ctx->k].wTime) {
|
||||
_ctx->MyActions[_ctx->k].dwLastTime += _ctx->MyActions[_ctx->k].wTime;
|
||||
|
||||
/* E' il momento di tirare il nostro dado virtuale, e controllare
|
||||
/* E' _ctx->il momento di tirare _ctx->il nostro dado virtuale, e controllare
|
||||
se la sorte e' dalla parte della idle action */
|
||||
byte randomVal = (byte)_vm->_randomSource.getRandomNumber(99);
|
||||
if (randomVal < MyActions[k].perc) {
|
||||
if (randomVal < _ctx->MyActions[_ctx->k].perc) {
|
||||
/* Controlliamo se c'e' una action in esecuzione sull'item */
|
||||
if ((bExecutingAction) && (nExecutingAction == MyActions[k].nItem))
|
||||
if ((bExecutingAction) && (nExecutingAction == _ctx->MyActions[_ctx->k].nItem))
|
||||
continue;
|
||||
|
||||
/* Controlliamo se c'e' gia' un'altra idle function in esecuzione
|
||||
sullo stesso item */
|
||||
for (i = 0; i < nRealItems; i++)
|
||||
if (MyThreads[i].nItem == MyActions[k].nItem)
|
||||
for (_ctx->i = 0; _ctx->i < _ctx->nRealItems; _ctx->i++)
|
||||
if (_ctx->MyThreads[_ctx->i].nItem == _ctx->MyActions[_ctx->k].nItem)
|
||||
break;
|
||||
|
||||
if (i < nRealItems)
|
||||
if (_ctx->i < _ctx->nRealItems)
|
||||
continue;
|
||||
|
||||
/* Ok, siamo gli unici :) */
|
||||
LockItems();
|
||||
curItem=lpmiItems+itemGetOrderFromNum(MyActions[k].nItem);
|
||||
_ctx->curItem=lpmiItems+itemGetOrderFromNum(_ctx->MyActions[_ctx->k].nItem);
|
||||
|
||||
/* Controlliamo se c'e' un esperessione WhenExecute */
|
||||
j=MyActions[k].nAction;
|
||||
if (curItem->Action[j].when != NULL)
|
||||
if (!EvaluateExpression(curItem->Action[j].when)) {
|
||||
_ctx->j=_ctx->MyActions[_ctx->k].nAction;
|
||||
if (_ctx->curItem->Action[_ctx->j].when != NULL)
|
||||
if (!EvaluateExpression(_ctx->curItem->Action[_ctx->j].when)) {
|
||||
UnlockItems();
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Ok, possiamo eseguire la azione. Per comodita' lo facciamo in
|
||||
un nuovo thread */
|
||||
newItem=(LPMPALITEM)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, sizeof(MPALITEM));
|
||||
if (newItem == false) {
|
||||
GlobalFree(MyThreads);
|
||||
GlobalFree(MyActions);
|
||||
ExitThread(0);
|
||||
// _endthread();
|
||||
_ctx->newItem = (LPMPALITEM)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, sizeof(MPALITEM));
|
||||
if (_ctx->newItem == false) {
|
||||
GlobalFree(_ctx->MyThreads);
|
||||
GlobalFree(_ctx->MyActions);
|
||||
|
||||
CORO_KILL_SELF();
|
||||
return;
|
||||
}
|
||||
|
||||
CopyMemory(newItem,curItem, sizeof(MPALITEM));
|
||||
CopyMemory(_ctx->newItem,_ctx->curItem, sizeof(MPALITEM));
|
||||
UnlockItems();
|
||||
|
||||
/* Copiamo l'azione nella #0 */
|
||||
// newItem->Action[0].nCmds = curItem->Action[j].nCmds;
|
||||
// CopyMemory(newItem->Action[0].CmdNum,curItem->Action[j].CmdNum,newItem->Action[0].nCmds*sizeof(newItem->Action[0].CmdNum[0]));
|
||||
newItem->dwRes=j;
|
||||
// _ctx->newItem->Action[0].nCmds = _ctx->curItem->Action[_ctx->j].nCmds;
|
||||
// CopyMemory(_ctx->newItem->Action[0].CmdNum,_ctx->curItem->Action[_ctx->j].CmdNum,_ctx->newItem->Action[0].nCmds*sizeof(_ctx->newItem->Action[0].CmdNum[0]));
|
||||
_ctx->newItem->dwRes=_ctx->j;
|
||||
|
||||
/* Creaiamo l'action thread. Provvedera' lui a liberare la memoria
|
||||
allocata per il nuovo item */
|
||||
for (i = 0; i < nRealItems; i++)
|
||||
if (MyThreads[i].nItem == 0)
|
||||
allocata per _ctx->il nuovo item */
|
||||
for (_ctx->i = 0; _ctx->i < _ctx->nRealItems; _ctx->i++)
|
||||
if (_ctx->MyThreads[_ctx->i].nItem == 0)
|
||||
break;
|
||||
|
||||
MyThreads[i].nItem=MyActions[k].nItem;
|
||||
_ctx->MyThreads[_ctx->i].nItem = _ctx->MyActions[_ctx->k].nItem;
|
||||
|
||||
// !!! Nuova gestione dei thread
|
||||
if ((MyThreads[i].hThread = CreateThread(NULL, 10240, (LPTHREAD_START_ROUTINE)ActionThread,(void *)newItem, 0, &dwId)) == NULL) {
|
||||
//if ((MyThreads[i].hThread=(void*)_beginthread(ActionThread, 10240,(void *)newItem))==(void*)-1)
|
||||
GlobalFree(newItem);
|
||||
GlobalFree(MyThreads);
|
||||
GlobalFree(MyActions);
|
||||
ExitThread(0);
|
||||
// _endthread();
|
||||
if ((_ctx->MyThreads[_ctx->i].hThread = _vm->_scheduler.createProcess(ActionThread, &_ctx->newItem, sizeof(LPMPALITEM))) == 0) {
|
||||
//if ((_ctx->MyThreads[_ctx->i].hThread=(void*)_beginthread(ActionThread, 10240,(void *)_ctx->newItem))==(void*)-1)
|
||||
GlobalFree(_ctx->newItem);
|
||||
GlobalFree(_ctx->MyThreads);
|
||||
GlobalFree(_ctx->MyActions);
|
||||
|
||||
CORO_KILL_SELF();
|
||||
return;
|
||||
}
|
||||
|
||||
/* Skippa tutte le idle action dello stesso item */
|
||||
|
@ -1251,16 +1264,16 @@ void PASCAL LocationPollThread(uint32 id) {
|
|||
}
|
||||
}
|
||||
|
||||
/* Chiude tutti i thread interni */
|
||||
/* Chiude tutti _ctx->i thread interni */
|
||||
|
||||
/*
|
||||
|
||||
CODICE OBSOLETO: ANDIAMO DI SKIP CHE RULLA
|
||||
|
||||
for (i = 0; i < nRealItems; i++)
|
||||
if (MyThreads[i].nItem != 0) {
|
||||
TerminateThread(MyThreads[i].hThread, 0);
|
||||
CloseHandle(MyThreads[i].hThread);
|
||||
for (_ctx->i = 0; _ctx->i < _ctx->nRealItems; _ctx->i++)
|
||||
if (_ctx->MyThreads[_ctx->i].nItem != 0) {
|
||||
TerminateThread(_ctx->MyThreads[_ctx->i].hThread, 0);
|
||||
CloseHandle(_ctx->MyThreads[_ctx->i].hThread);
|
||||
}
|
||||
*/
|
||||
|
||||
|
@ -1268,23 +1281,28 @@ void PASCAL LocationPollThread(uint32 id) {
|
|||
// FIXME: Convert to co-routine
|
||||
lplpFunctions[200](nullContext, 0, 0, 0, 0);
|
||||
|
||||
for (i = 0; i < nRealItems; i++)
|
||||
if (MyThreads[i].nItem != 0) {
|
||||
if (WaitForSingleObject(MyThreads[i].hThread,5000) != WAIT_OBJECT_0)
|
||||
TerminateThread(MyThreads[i].hThread, 0);
|
||||
for (_ctx->i = 0; _ctx->i < _ctx->nRealItems; _ctx->i++)
|
||||
if (_ctx->MyThreads[_ctx->i].nItem != 0) {
|
||||
CORO_INVOKE_3(_vm->_scheduler.waitForSingleObject, _ctx->MyThreads[_ctx->i].hThread, 5000, &_ctx->delayExpired);
|
||||
|
||||
CloseHandle(MyThreads[i].hThread);
|
||||
/*
|
||||
//if (result != WAIT_OBJECT_0)
|
||||
if (_ctx->delayExpired)
|
||||
TerminateThread(_ctx->MyThreads[_ctx->i].hThread, 0);
|
||||
*/
|
||||
_vm->_scheduler.killMatchingProcess(_ctx->MyThreads[_ctx->i].hThread);
|
||||
}
|
||||
|
||||
// Set idle skip off
|
||||
// FIXME: Convert to co-routine
|
||||
lplpFunctions[201](nullContext, 0, 0, 0, 0);
|
||||
CORO_INVOKE_4(lplpFunctions[201], 0, 0, 0, 0);
|
||||
|
||||
/* Abbiamo finito */
|
||||
GlobalFree(MyThreads);
|
||||
GlobalFree(MyActions);
|
||||
ExitThread(1);
|
||||
//endthread();
|
||||
GlobalFree(_ctx->MyThreads);
|
||||
GlobalFree(_ctx->MyActions);
|
||||
|
||||
CORO_KILL_SELF();
|
||||
|
||||
CORO_END_CODE;
|
||||
}
|
||||
|
||||
|
||||
|
@ -1516,7 +1534,7 @@ static HANDLE DoAction(uint32 nAction, uint32 ordItem, uint32 dwParam) {
|
|||
LPMPALITEM item = lpmiItems;
|
||||
int i;
|
||||
LPMPALITEM newitem;
|
||||
PROCESS *h;
|
||||
uint32 h;
|
||||
|
||||
item+=ordItem;
|
||||
Common::String buf = Common::String::format("Status.%u", item->nObj);
|
||||
|
@ -1552,10 +1570,10 @@ static HANDLE DoAction(uint32 nAction, uint32 ordItem, uint32 dwParam) {
|
|||
// 0 dell'item, e poi liberera' la memoria con la GlobalFree()
|
||||
|
||||
// !!! New thread management
|
||||
if ((h = g_scheduler->createProcess(ActionThread, newitem)) == NULL)
|
||||
if ((h = g_scheduler->createProcess(ActionThread, &newitem, sizeof(LPMPALITEM))) == NULL)
|
||||
return INVALID_HANDLE_VALUE;
|
||||
|
||||
if ((h = g_scheduler->createProcess(ShutUpActionThread, &h->pid, sizeof(int))) == NULL)
|
||||
if ((h = g_scheduler->createProcess(ShutUpActionThread, &h, sizeof(uint32))) == NULL)
|
||||
return INVALID_HANDLE_VALUE;
|
||||
|
||||
/*
|
||||
|
@ -2182,7 +2200,7 @@ void EXPORT mpalInstallItemIrq(LPITEMIRQFUNCTION lpiifCus) {
|
|||
*
|
||||
\****************************************************************************/
|
||||
|
||||
bool EXPORT mpalStartIdlePoll(int nLoc) {
|
||||
bool mpalStartIdlePoll(int nLoc) {
|
||||
uint32 i;
|
||||
uint32 dwId;
|
||||
|
||||
|
@ -2196,7 +2214,7 @@ bool EXPORT mpalStartIdlePoll(int nLoc) {
|
|||
|
||||
hEndPollingLocations[i] = CreateEvent(NULL, true, false, NULL);
|
||||
// !!! Nuova gestione dei thread
|
||||
if ((PollingThreads[i] = CreateThread(NULL, 10240, (LPTHREAD_START_ROUTINE)LocationPollThread,(void *)i, 0, &dwId)) == NULL)
|
||||
if ((PollingThreads[i] = _vm->_scheduler.createProcess(LocationPollThread, &i, sizeof(uint32))) == 0)
|
||||
// if ((hEndPollingLocations[i]=(void*)_beginthread(LocationPollThread, 10240,(void *)i))==(void*)-1)
|
||||
return false;
|
||||
|
||||
|
@ -2222,22 +2240,32 @@ bool EXPORT mpalStartIdlePoll(int nLoc) {
|
|||
*
|
||||
\****************************************************************************/
|
||||
|
||||
bool EXPORT mpalEndIdlePoll(int nLoc) {
|
||||
uint32 i;
|
||||
void mpalEndIdlePoll(CORO_PARAM, int nLoc, bool *result) {
|
||||
CORO_BEGIN_CONTEXT;
|
||||
int i;
|
||||
CORO_END_CONTEXT(_ctx);
|
||||
|
||||
for (i = 0; i < MAXPOLLINGLOCATIONS; i++)
|
||||
if (nPollingLocations[i] == (uint32)nLoc) {
|
||||
SetEvent(hEndPollingLocations[i]);
|
||||
CORO_BEGIN_CODE(_ctx);
|
||||
|
||||
WaitForSingleObject(PollingThreads[i], INFINITE);
|
||||
for (_ctx->i = 0; _ctx->i < MAXPOLLINGLOCATIONS; _ctx->i++) {
|
||||
if (nPollingLocations[_ctx->i] == (uint32)nLoc) {
|
||||
SetEvent(hEndPollingLocations[_ctx->i]);
|
||||
|
||||
CloseHandle(hEndPollingLocations[i]);
|
||||
nPollingLocations[i] = 0;
|
||||
CORO_INVOKE_2(_vm->_scheduler.waitForSingleObject, PollingThreads[_ctx->i], INFINITE);
|
||||
|
||||
return true;
|
||||
CloseHandle(hEndPollingLocations[_ctx->i]);
|
||||
nPollingLocations[_ctx->i] = 0;
|
||||
|
||||
if (result)
|
||||
*result = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
if (result)
|
||||
*result = false;
|
||||
|
||||
CORO_END_CODE;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -704,8 +704,7 @@ bool EXPORT mpalStartIdlePoll(int nLoc);
|
|||
*
|
||||
\****************************************************************************/
|
||||
|
||||
bool EXPORT mpalEndIdlePoll(int nLoc);
|
||||
|
||||
void mpalEndIdlePoll(CORO_PARAM, int nLoc, bool *result);
|
||||
|
||||
|
||||
/****************************************************************************\
|
||||
|
|
|
@ -296,8 +296,9 @@ void Scheduler::giveWay(PPROCESS pReSchedProc) {
|
|||
*
|
||||
* @param pid Process identifier
|
||||
* @param duration Duration in milliseconds
|
||||
* @param expired Set to true if delay period expired
|
||||
*/
|
||||
void Scheduler::waitForSingleObject(CORO_PARAM, int pid, int duration) {
|
||||
void Scheduler::waitForSingleObject(CORO_PARAM, int pid, int duration, bool *expired) {
|
||||
CORO_BEGIN_CONTEXT;
|
||||
uint32 endTime;
|
||||
PROCESS *pProc;
|
||||
|
@ -306,6 +307,8 @@ void Scheduler::waitForSingleObject(CORO_PARAM, int pid, int duration) {
|
|||
CORO_BEGIN_CODE(_ctx);
|
||||
|
||||
_ctx->endTime = (duration == INFINITE) ? INFINITE : g_system->getMillis() + duration;
|
||||
if (expired)
|
||||
*expired = false;
|
||||
|
||||
// Outer loop for doing checks until expiry
|
||||
while (g_system->getMillis() < _ctx->endTime) {
|
||||
|
@ -314,9 +317,13 @@ void Scheduler::waitForSingleObject(CORO_PARAM, int pid, int duration) {
|
|||
while ((_ctx->pProc != NULL) && (_ctx->pProc->pid == pid))
|
||||
_ctx->pProc = _ctx->pProc->pNext;
|
||||
|
||||
if (_ctx->pProc == NULL)
|
||||
if (_ctx->pProc == NULL) {
|
||||
// No match process found, so it's okay to break out of loop
|
||||
if (expired)
|
||||
*expired = true;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Sleep until the next cycle
|
||||
CORO_SLEEP(1);
|
||||
|
@ -333,7 +340,7 @@ void Scheduler::waitForSingleObject(CORO_PARAM, int pid, int duration) {
|
|||
* @param pParam process specific info
|
||||
* @param sizeParam size of process specific info
|
||||
*/
|
||||
PROCESS *Scheduler::createProcess(CORO_ADDR coroAddr, const void *pParam, int sizeParam) {
|
||||
uint32 Scheduler::createProcess(CORO_ADDR coroAddr, const void *pParam, int sizeParam) {
|
||||
PROCESS *pProc;
|
||||
|
||||
// get a free process
|
||||
|
@ -394,7 +401,7 @@ PROCESS *Scheduler::createProcess(CORO_ADDR coroAddr, const void *pParam, int si
|
|||
}
|
||||
|
||||
// return created process
|
||||
return pProc;
|
||||
return pProc->pid;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -111,10 +111,10 @@ public:
|
|||
void rescheduleAll();
|
||||
void reschedule(PPROCESS pReSchedProc = NULL);
|
||||
void giveWay(PPROCESS pReSchedProc = NULL);
|
||||
void waitForSingleObject(CORO_PARAM, int pid, int duration);
|
||||
void waitForSingleObject(CORO_PARAM, int pid, int duration, bool *delay = NULL);
|
||||
|
||||
PROCESS *createProcess(CORO_ADDR coroAddr, const void *pParam, int sizeParam);
|
||||
PROCESS *createProcess(CORO_ADDR coroAddr, const void *pParam) {
|
||||
uint32 createProcess(CORO_ADDR coroAddr, const void *pParam, int sizeParam);
|
||||
uint32 createProcess(CORO_ADDR coroAddr, const void *pParam) {
|
||||
return createProcess(coroAddr, &pParam, sizeof(void *));
|
||||
}
|
||||
void killProcess(PROCESS *pKillProc);
|
||||
|
|
|
@ -59,11 +59,19 @@ namespace Tony {
|
|||
|
||||
bool RMTony::m_bAction = false;
|
||||
|
||||
uint32 RMTony::WaitEndOfAction(HANDLE hThread) {
|
||||
WaitForSingleObject(hThread, INFINITE);
|
||||
void RMTony::WaitEndOfAction(CORO_PARAM, const void *param) {
|
||||
CORO_BEGIN_CONTEXT;
|
||||
CORO_END_CONTEXT(_ctx);
|
||||
|
||||
uint32 pid = *(const uint32 *)param;
|
||||
|
||||
CORO_BEGIN_CODE(_ctx);
|
||||
|
||||
CORO_INVOKE_2(_vm->_scheduler.waitForSingleObject, pid, INFINITE);
|
||||
|
||||
m_bAction = false;
|
||||
|
||||
return 1;
|
||||
CORO_END_CODE;
|
||||
}
|
||||
|
||||
RMGfxSourceBuffer *RMTony::NewItemSpriteBuffer(int dimx, int dimy, bool bPreRLE) {
|
||||
|
@ -205,6 +213,7 @@ void RMTony::MoveAndDoAction(RMPoint dst, RMItem *item, int nAction, int nAction
|
|||
|
||||
void RMTony::ExecuteAction(int nAction, int nActionItem, int nParm) {
|
||||
HANDLE hThread;
|
||||
uint32 pid;
|
||||
|
||||
if (nAction == TA_COMBINE) {
|
||||
hThread = mpalQueryDoAction(TA_COMBINE, nParm, nActionItem);
|
||||
|
@ -229,27 +238,28 @@ void RMTony::ExecuteAction(int nAction, int nActionItem, int nParm) {
|
|||
}
|
||||
|
||||
if (hThread != INVALID_HANDLE_VALUE) {
|
||||
uint32 id;
|
||||
m_bAction = true;
|
||||
CreateThread(NULL, 10240,(LPTHREAD_START_ROUTINE)WaitEndOfAction, (void *)hThread, 0, &id);
|
||||
pid = (uint32)hThread;
|
||||
_vm->_scheduler.createProcess(WaitEndOfAction, &pid, sizeof(uint32));
|
||||
hActionThread = hThread;
|
||||
} else if (nAction != TA_GOTO) {
|
||||
uint32 id;
|
||||
|
||||
if (nAction == TA_TALK) {
|
||||
hThread = mpalQueryDoAction(6, 1, 0);
|
||||
m_bAction = true;
|
||||
CreateThread(NULL,10240,(LPTHREAD_START_ROUTINE)WaitEndOfAction, (void *)hThread,0,&id);
|
||||
pid = (uint32)hThread;
|
||||
_vm->_scheduler.createProcess(WaitEndOfAction, &pid, sizeof(uint32));
|
||||
hActionThread = hThread;
|
||||
} else if (nAction == TA_PALESATI) {
|
||||
hThread = mpalQueryDoAction(7, 1, 0);
|
||||
m_bAction = true;
|
||||
CreateThread(NULL,10240,(LPTHREAD_START_ROUTINE)WaitEndOfAction,(void *)hThread, 0, &id);
|
||||
pid = (uint32)hThread;
|
||||
_vm->_scheduler.createProcess(WaitEndOfAction, &pid, sizeof(uint32));
|
||||
hActionThread=hThread;
|
||||
} else {
|
||||
hThread = mpalQueryDoAction(5, 1, 0);
|
||||
m_bAction = true;
|
||||
CreateThread(NULL, 10240, (LPTHREAD_START_ROUTINE)WaitEndOfAction, (void *)hThread, 0, &id);
|
||||
pid = (uint32)hThread;
|
||||
_vm->_scheduler.createProcess(WaitEndOfAction, &pid, sizeof(uint32));
|
||||
hActionThread = hThread;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,6 +49,7 @@
|
|||
#define TONY_TONYCHAR_H
|
||||
|
||||
#include "tony/mpal/stubs.h"
|
||||
#include "tony/coroutine.h"
|
||||
#include "tony/loc.h"
|
||||
|
||||
namespace Tony {
|
||||
|
@ -128,8 +129,8 @@ protected:
|
|||
// Overload dell'allocazione degli sprites per cambiare il tipo
|
||||
virtual RMGfxSourceBuffer* NewItemSpriteBuffer(int dimx, int dimy, bool bPreRLE);
|
||||
|
||||
// Thread che aspetta la fine di un azione
|
||||
static uint32 WaitEndOfAction(HANDLE hThread);
|
||||
// Thread which waits for the end of an action
|
||||
static void WaitEndOfAction(CORO_PARAM, const void *param);
|
||||
|
||||
public: // per farlo rialzare, altrimenti private
|
||||
enum PATTERNS {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue