You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
210 lines
7.0 KiB
210 lines
7.0 KiB
/** |
|
* This should be considered an internal class and a detail of |
|
* implementation. |
|
* An object that is created when user tries to query database. |
|
* It contains a delegate `connect()` that will be called when query is |
|
* completed and will self-destruct afterwards. Concrete delegates are |
|
* declared in child classes of this `DBTask`, since they can have different |
|
* signatures, depending on the query. |
|
* Copyright 2021-2023 Anton Tarasenko |
|
*------------------------------------------------------------------------------ |
|
* This file is part of Acedia. |
|
* |
|
* Acedia is free software: you can redistribute it and/or modify |
|
* it under the terms of the GNU General Public License as published by |
|
* the Free Software Foundation, version 3 of the License, or |
|
* (at your option) any later version. |
|
* |
|
* Acedia is distributed in the hope that it will be useful, |
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
* GNU General Public License for more details. |
|
* |
|
* You should have received a copy of the GNU General Public License |
|
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
|
*/ |
|
class DBTask extends AcediaObject |
|
dependson(Database) |
|
abstract; |
|
|
|
/** |
|
* Life of instances of this class is supposed to go like so: |
|
* 1. Get created and returned to the user that made database query so |
|
* that he can setup a delegate that will receive the result; |
|
* 2. Wait until database query result is ready AND all previous tasks |
|
* have completed; |
|
* 3. Call it's `connect()` delegate with query results; |
|
* 4. Deallocate itself. |
|
* |
|
* Task is determined ready when it's `DBQueryResult` variable was set. |
|
* |
|
* This class IS NOT supposed to be accessed by user at all - this is simply |
|
* an auxiliary construction that allows us to make calls to the database |
|
* like so: `db.ReadData(...).connect = handler;`. |
|
* |
|
* Since every query can have it's own set of returning parameters - |
|
* signature of `connect()` method can vary from task to task. |
|
* For this reason we define it in child classes of `BDTask` that specialize in |
|
* particular query. |
|
*/ |
|
|
|
var private DBTask previousTask; |
|
// These allows us to detect when previous task got completed (deallocated) |
|
var private int previousTaskLifeVersion; |
|
|
|
var private Database.DBQueryResult taskResult; |
|
var private bool isReadyToComplete; |
|
var private int requestID; |
|
|
|
var private LoggerAPI.Definition errLoopInTaskChain; |
|
|
|
protected function Finalizer() |
|
{ |
|
if (previousTask != none) { |
|
previousTask.FreeSelf(previousTaskLifeVersion); |
|
} |
|
previousTask = none; |
|
previousTaskLifeVersion = -1; |
|
isReadyToComplete = false; |
|
requestID = 0; |
|
} |
|
|
|
/** |
|
* Returns ID of the request set inside `SetResult()` for the caller `DBTask`. |
|
* |
|
* @return ID of the request set inside `SetResult()` for the caller `DBTask`. |
|
* If `SetResult()` wasn't yet called returns `0`. |
|
*/ |
|
protected function int GetRequestID() |
|
{ |
|
return requestID; |
|
} |
|
|
|
/** |
|
* Sets `DBQueryResult` for the caller task. |
|
* |
|
* Having previous task assigned is not required for the caller task to |
|
* be completed, since it can be the first task. |
|
* |
|
* @param task Task that has to be completed before this one can. |
|
*/ |
|
public final function SetPreviousTask(DBTask task) |
|
{ |
|
previousTask = task; |
|
if (previousTask != none) { |
|
previousTaskLifeVersion = previousTask.GetLifeVersion(); |
|
} |
|
} |
|
|
|
/** |
|
* Returns `DBQueryResult` assigned to the caller `DBTask`. |
|
* |
|
* This method should only be called after `SetResult()`, otherwise it's |
|
* behavior and return result should be considered undefined. |
|
* |
|
* @return `DBQueryResult` assigned to the caller `DBTask`. |
|
*/ |
|
public final function Database.DBQueryResult GetResult() |
|
{ |
|
return taskResult; |
|
} |
|
|
|
/** |
|
* Assigns `DBQueryResult` for the caller task. |
|
* |
|
* Every single task has to be assigned one and cannot be completed before |
|
* it does. |
|
* |
|
* This value can be assigned several times and the last assigned value will |
|
* be used. |
|
* |
|
* @param result Result of the query, relevant to the caller task. |
|
* @param requestID ID of the request this task is responding to, specified |
|
* at the time request was made. |
|
*/ |
|
public final function SetResult( |
|
Database.DBQueryResult result, |
|
optional int completedRequestID) |
|
{ |
|
taskResult = result; |
|
isReadyToComplete = true; |
|
requestID = completedRequestID; |
|
} |
|
|
|
/** |
|
* Override this to call `connect()` delegate declared in child classes. |
|
* Since this base class does not itself have `connect()` delegate declared - |
|
* this method cannot be implemented here. |
|
*/ |
|
protected function CompleteSelf(Database source) {} |
|
|
|
/** |
|
* Attempts to complete this task. |
|
* Can only succeed iff caller task both has necessary data to complete it's |
|
* query and all previous tasks have completed. |
|
* |
|
* @param source Database that will be passed to `DBTask`'s delegate as |
|
* its cause. |
|
*/ |
|
public final function TryCompleting(optional Database source) |
|
{ |
|
local int i; |
|
local array<DBTask> tasksQueue; |
|
tasksQueue = BuildRequiredTasksQueue(); |
|
// Queue is built backwards: tasks that have to be completed first are |
|
// at the end of the array |
|
for (i = tasksQueue.length - 1; i >= 0; i -= 1) |
|
{ |
|
if (tasksQueue[i].isReadyToComplete) |
|
{ |
|
tasksQueue[i].CompleteSelf(source); |
|
_.memory.Free(tasksQueue[i]); |
|
} |
|
else { |
|
break; |
|
} |
|
} |
|
} |
|
|
|
// We do not know how deep `previousTask`-based chain will go, so we |
|
// will store tasks that have to complete last earlier in the array. |
|
private final function array<DBTask> BuildRequiredTasksQueue() |
|
{ |
|
local int i; |
|
local int expectedLifeVersion; |
|
local bool loopDetected; |
|
local DBTask nextRequiredTask; |
|
local array<DBTask> tasksQueue; |
|
nextRequiredTask = self; |
|
tasksQueue[0] = nextRequiredTask; |
|
while (nextRequiredTask.previousTask != none) |
|
{ |
|
expectedLifeVersion = nextRequiredTask.previousTaskLifeVersion; |
|
nextRequiredTask = nextRequiredTask.previousTask; |
|
if (nextRequiredTask.GetLifeVersion() != expectedLifeVersion) { |
|
break; |
|
} |
|
for (i = 0; i < tasksQueue.length; i += 1) |
|
{ |
|
if (nextRequiredTask == tasksQueue[i]) |
|
{ |
|
loopDetected = true; |
|
break; |
|
} |
|
} |
|
if (!loopDetected) { |
|
tasksQueue[tasksQueue.length] = nextRequiredTask; |
|
} |
|
else |
|
{ |
|
_.logger.Auto(errLoopInTaskChain).ArgClass(nextRequiredTask.class); |
|
break; |
|
} |
|
} |
|
return tasksQueue; |
|
} |
|
|
|
defaultproperties |
|
{ |
|
errLoopInTaskChain = (l=LOG_Error,m="`DBTask` of class `%1` required itself to complete. This might cause database to get damaged unexpectedly. Please report this to the developer.") |
|
} |