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.
189 lines
6.2 KiB
189 lines
6.2 KiB
3 years ago
|
/**
|
||
|
* 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 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 LoggerAPI.Definition errLoopInTaskChain;
|
||
|
|
||
|
protected function Finalizer()
|
||
|
{
|
||
|
if (previousTask != none) {
|
||
|
previousTask.FreeSelf(previousTaskLifeVersion);
|
||
|
}
|
||
|
previousTask = none;
|
||
|
previousTaskLifeVersion = -1;
|
||
|
isReadyToComplete = false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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.
|
||
|
*/
|
||
|
public final function SetResult(Database.DBQueryResult result)
|
||
|
{
|
||
|
taskResult = result;
|
||
|
isReadyToComplete = true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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() {}
|
||
|
|
||
|
/**
|
||
|
* 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.
|
||
|
*/
|
||
|
public final function TryCompleting()
|
||
|
{
|
||
|
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();
|
||
|
_.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.")
|
||
|
}
|