/**
 * Author: dkanus
 * Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore
 * License: GPL
 * Copyright 2020-2024 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 MemoryApi extends AcediaObject;

//! API that provides functions for managing object of classes, derived from
//! [`AcediaObject`].
//!
//! This is most-basic API that must be created before anything else in Acedia,
//! since it is responsible for the proper creation of [`AcediaObject`]s.
//! It takes care of managing their object pools, as well as ensuring that
//! constructors and finalizers are called properly.

// Store all created pools, so that we can quickly forget stored objects upon
// garbage collection.
var private array<AcediaObjectPool> registeredPools;

/// Forgets about all stored (deallocated) object references in registered
/// object pools.
protected function DropPools() {
    local int i;

    registeredPools = default.registeredPools;
    for (i = 0; i < registeredPools.length; i += 1) {
        if (registeredPools[i] == none) {
            continue;
        }
        registeredPools[i].Clear();
    }
}

/// Creates a class instance from its [`BaseText`] representation.
///
/// Does not generate log messages upon failure.
public function class<Object> LoadClass(BaseText classReference) {
    if (classReference == none) {
        return none;
    }
    return class<Object>(DynamicLoadObject(classReference.ToString(), class'Class', true));
}

/// Creates a class instance from its `string` representation.
///
/// Does not generate log messages upon failure.
public function class<Object> LoadClass_S(string classReference) {
    return class<Object>(DynamicLoadObject(classReference, class'Class', true));
}

/// Checks whether class with a given name exist.
public function bool ClassExists(BaseText classReference) {
    local class<Object> classToCheck;

    classToCheck = LoadClass(classReference);
    return (classToCheck != none);
}

/// Checks whether class with a given name exist.
public function bool ClassExists_S(string classReference) {
    local class<Object> classToCheck;

    classToCheck = LoadClass_S(classReference);
    return (classToCheck != none);
}

/// Creates a new [`AcediaObject`] of a given subclass using pool (if permitted
/// by settings) and calling its constructor.
///
/// If Acedia's object does make use of object pools, this method guarantees to
/// return last pooled object (in a LIFO queue), unless [`forceNewInstance`] is
/// set to `true`.
///
/// Return value will only be `none` if [`classToAllocate`] is `none` or
/// abstract.
public function AcediaObject Allocate(
    class<AcediaObject> classToAllocate,
    optional bool forceNewInstance
) {
    local AcediaObject allocatedObject;
    local AcediaObjectPool relevantPool;

    if (classToAllocate == none) {
        return none;
    }
    //  Try using pool first (but only if new instance is not required)
    if (!forceNewInstance) {
        relevantPool = classToAllocate.static._getPool();
        //  `relevantPool == none` is expected if object / actor of is setup to
        //  not use object pools.
        if (relevantPool != none) {
            allocatedObject = relevantPool.Fetch();
        }
    }
    //  If pools did not work - simply create object manually
    if (allocatedObject == none) {
        allocatedObject = (new classToAllocate);
    }
    //  Allocation through `new` cannot fail, so its safe to call constructor
    allocatedObject._constructor();
    return allocatedObject;
}

/// Creates a new [`AcediaObject`] of a given subclass using pool (if permitted
/// by settings) and calling its constructor.
///
/// If Acedia's object does make use of object pools, this method guarantees to
/// return last pooled object (in a LIFO queue), unless [`forceNewInstance`] is
/// set to `true`.
///
/// Return value will only be `none` if [`refToClassToAllocate`] is `none`,
/// doesn't refer to an existing class or refers to an abstract class.
public function AcediaObject AllocateByReference(
    BaseText refToClassToAllocate,
    optional bool forceNewInstance
) {
    local class<Object> classToAllocate;

    classToAllocate = LoadClass(refToClassToAllocate);
    return Allocate(class<AcediaObject>(classToAllocate), forceNewInstance);
}

/// Creates a new [`AcediaObject`] of a given subclass using pool (if permitted
/// by settings) and calling its constructor.
///
/// If Acedia's object does make use of object pools, this method guarantees to
/// return last pooled object (in a LIFO queue), unless [`forceNewInstance`]
/// is set to `true`.
///
/// Return value will only be `none` if [`refToClassToAllocate`] is `none`,
/// doesn't refer to an existing class or refers to an abstract class.
public function AcediaObject AllocateByReference_S(
    string refToClassToAllocate,
    optional bool forceNewInstance
) {
    local class<Object> classToAllocate;

    classToAllocate = LoadClass_S(refToClassToAllocate);
    return Allocate(class<AcediaObject>(classToAllocate), forceNewInstance);
}

/// Releases one reference to a given [`AcediaObject`], calling its finalizers
/// in case all references were released.
///
/// Method will attempt to store [`objectToRelease`] in its object pool once
/// deallocated, unless it is forbidden by its class' settings.
public function Free(AcediaObject objectToRelease) {
    local AcediaObjectPool relevantPool;

    if (objectToRelease == none)        return;
    if (!objectToRelease.IsAllocated()) return;

    objectToRelease._deref();
    // Finalize object if all of its references are gone
    if (objectToRelease._getRefCount() <= 0) {
        relevantPool = objectToRelease._getPool();
        objectToRelease._finalizer();
        if (relevantPool != none) {
            relevantPool.Store(objectToRelease);
        }
    }
}

/// Checks if given object is allocated.
///
/// This is a convenience method that also performs `none` check before
/// directly calling [`AcediaObject::IsAllocated()`].
public function bool IsReferenceAllocated(AcediaObject objectToCheck) {
    if (objectToCheck == none)          return false;
    if (!objectToCheck.IsAllocated())   return false;

    return true;
}

/// Releases references to given [`AcediaObject`]s, calling their finalizers in
/// case all references were released.
///
/// Method will attempt to store released objects in its object pool once
/// deallocated, unless it is forbidden by its class' settings.
///
/// You might want to consider using [`MemoryAPI::Kill2()`] method instead,
/// since it automatically erases values stored in the references, reducing
/// possible errors.
public function Free2(AcediaObject objectToRelease1, AcediaObject objectToRelease2) {
    Free(objectToRelease1);
    Free(objectToRelease2);
}

/// Releases references to given [`AcediaObject`]s, calling their finalizers in
/// case all references were released.
///
/// Method will attempt to store released objects in its object pool once
/// deallocated, unless it is forbidden by its class' settings.
///
/// You might want to consider using [`MemoryAPI::Kill3()`] method instead,
/// since it automatically erases values stored in the references, reducing
/// possible errors.
public function Free3(
    AcediaObject objectToRelease1,
    AcediaObject objectToRelease2,
    AcediaObject objectToRelease3
) {
    Free(objectToRelease1);
    Free(objectToRelease2);
    Free(objectToRelease3);
}

/// Releases references to given [`AcediaObject`]s, calling their finalizers in
/// case all references were released.
///
/// Method will attempt to store released objects in its object pool once
/// deallocated, unless it is forbidden by its class' settings.
///
/// You might want to consider using [`MemoryAPI::Kill4()`] method instead,
/// since it automatically erases values stored in the references, reducing
/// possible errors.
public function Free4(
    AcediaObject objectToRelease1,
    AcediaObject objectToRelease2,
    AcediaObject objectToRelease3,
    AcediaObject objectToRelease4
) {
    Free(objectToRelease1);
    Free(objectToRelease2);
    Free(objectToRelease3);
    Free(objectToRelease4);
}

/// Releases references to given [`AcediaObject`]s, calling their finalizers in
/// case all references were released.
///
/// Method will attempt to store released objects in its object pool once
/// deallocated, unless it is forbidden by its class' settings.
///
/// You might want to consider using [`MemoryAPI::Kill5()`] method instead,
/// since it automatically erases values stored in the references, reducing
/// possible errors.
public function Free5(
    AcediaObject objectToRelease1,
    AcediaObject objectToRelease2,
    AcediaObject objectToRelease3,
    AcediaObject objectToRelease4,
    AcediaObject objectToRelease5
) {
    Free(objectToRelease1);
    Free(objectToRelease2);
    Free(objectToRelease3);
    Free(objectToRelease4);
    Free(objectToRelease5);
}

/// Releases references to given [`AcediaObject`]s, calling their finalizers in
/// case all references were released.
///
/// Method will attempt to store released objects in its object pool once
/// deallocated, unless it is forbidden by its class' settings.
///
/// You might want to consider using [`MemoryAPI::Kill6()`] method instead,
/// since it automatically erases values stored in the references, reducing
/// possible errors.
public function Free6(
    AcediaObject objectToRelease1,
    AcediaObject objectToRelease2,
    AcediaObject objectToRelease3,
    AcediaObject objectToRelease4,
    AcediaObject objectToRelease5,
    AcediaObject objectToRelease6
) {
    Free(objectToRelease1);
    Free(objectToRelease2);
    Free(objectToRelease3);
    Free(objectToRelease4);
    Free(objectToRelease5);
    Free(objectToRelease6);
}

/// Releases references to given [`AcediaObject`]s, calling their finalizers in
/// case all references were released.
///
/// Method will attempt to store released objects in its object pool once
/// deallocated, unless it is forbidden by its class' settings.
///
/// You might want to consider using [`MemoryAPI::Kill7()`] method instead,
/// since it automatically erases values stored in the references, reducing
/// possible errors.
public function Free7(
    AcediaObject objectToRelease1,
    AcediaObject objectToRelease2,
    AcediaObject objectToRelease3,
    AcediaObject objectToRelease4,
    AcediaObject objectToRelease5,
    AcediaObject objectToRelease6,
    AcediaObject objectToRelease7
) {
    Free(objectToRelease1);
    Free(objectToRelease2);
    Free(objectToRelease3);
    Free(objectToRelease4);
    Free(objectToRelease5);
    Free(objectToRelease6);
    Free(objectToRelease7);
}

/// Releases one reference for each [`AcediaObject`] inside the given array
/// [`objectsToRelease`],
/// calling finalizers for the ones that got all of their references released.
///
/// Method will attempt to store objects inside [`objectsToRelease`] in their
/// object pools, unless it is forbidden by their class' settings.
///
/// You might want to consider using [`MemoryAPI::KillMany()`] method instead,
/// since it automatically forgets contents of the array, reducing possible
/// errors.
public function FreeMany(array<AcediaObject> objectsToRelease) {
    local int i;

    for (i = 0; i < objectsToRelease.length; i += 1) {
        Free(objectsToRelease[i]);
    }
}

/// Releases and erases one reference to a given [`AcediaObject`], calling its
/// finalizers in case all references were released.
///
/// Method will attempt to store [`objectToRelease`] in its object pool once
/// deallocated, unless it is forbidden by its class' settings.
///
/// This method automatically erases given reference.
/// If argument cannot be passed by reference or, for some reason, you'd like
/// to keep the reference, use [`MemoryAPI::Free()`] method instead.
public function Kill(out AcediaObject objectToRelease) {
    Free(objectToRelease);
    objectToRelease = none;
}

/// Releases and erases references to given [`AcediaObject`]s, calling their
/// finalizers in case all references were released.
///
/// Method will attempt to store released objects in its object pool once
/// deallocated, unless it is forbidden by its class' settings.
///
/// This method automatically erases given references.
/// If arguments cannot be passed by reference or, for some reason, you'd like
/// to keep their references, use [`MemoryAPI::Free2()`] method instead.
public function Kill2(out AcediaObject objectToRelease1, out AcediaObject objectToRelease2) {
    Kill(objectToRelease1);
    Kill(objectToRelease2);
}

/// Releases and erases references to given [`AcediaObject`]s, calling their
/// finalizers in case all references were released.
///
/// Method will attempt to store released objects in its object pool once
/// deallocated, unless it is forbidden by its class' settings.
///
/// This method automatically erases given references.
/// If arguments cannot be passed by reference or, for some reason, you'd like
/// to keep their references, use [`MemoryAPI::Free3()`] method instead.
public function Kill3(
    out AcediaObject objectToRelease1,
    out AcediaObject objectToRelease2,
    out AcediaObject objectToRelease3
) {
    Kill(objectToRelease1);
    Kill(objectToRelease2);
    Kill(objectToRelease3);
}

/// Releases and erases references to given [`AcediaObject`]s, calling their
/// finalizers in case all references were released.
///
/// Method will attempt to store released objects in its object pool once
/// deallocated, unless it is forbidden by its class' settings.
///
/// This method automatically erases given references.
/// If arguments cannot be passed by reference or, for some reason, you'd like
/// to keep their references, use [`MemoryAPI::Free4()`] method instead.
public function Kill4(
    out AcediaObject objectToRelease1,
    out AcediaObject objectToRelease2,
    out AcediaObject objectToRelease3,
    out AcediaObject objectToRelease4
) {
    Kill(objectToRelease1);
    Kill(objectToRelease2);
    Kill(objectToRelease3);
    Kill(objectToRelease4);
}

/// Releases and erases references to given [`AcediaObject`]s, calling their
/// finalizers in case all references were released.
///
/// Method will attempt to store released objects in its object pool once
/// deallocated, unless it is forbidden by its class' settings.
///
/// This method automatically erases given references.
/// If arguments cannot be passed by reference or, for some reason, you'd like
/// to keep their references, use [`MemoryAPI::Free5()`] method instead.
public function Kill5(
    out AcediaObject objectToRelease1,
    out AcediaObject objectToRelease2,
    out AcediaObject objectToRelease3,
    out AcediaObject objectToRelease4,
    out AcediaObject objectToRelease5
) {
    Kill(objectToRelease1);
    Kill(objectToRelease2);
    Kill(objectToRelease3);
    Kill(objectToRelease4);
    Kill(objectToRelease5);
}

/// Releases and erases references to given [`AcediaObject`]s, calling their
/// finalizers in case all references were released.
///
/// Method will attempt to store released objects in its object pool once
/// deallocated, unless it is forbidden by its class' settings.
///
/// This method automatically erases given references.
/// If arguments cannot be passed by reference or, for some reason, you'd like
/// to keep their references, use [`MemoryAPI::Free6()`] method instead.
public function Kill6(
    out AcediaObject objectToRelease1,
    out AcediaObject objectToRelease2,
    out AcediaObject objectToRelease3,
    out AcediaObject objectToRelease4,
    out AcediaObject objectToRelease5,
    out AcediaObject objectToRelease6
) {
    Kill(objectToRelease1);
    Kill(objectToRelease2);
    Kill(objectToRelease3);
    Kill(objectToRelease4);
    Kill(objectToRelease5);
    Kill(objectToRelease6);
}

/// Releases and erases references to given [`AcediaObject`]s, calling their
/// finalizers in case all references were released.
///
/// Method will attempt to store released objects in its object pool once
/// deallocated, unless it is forbidden by its class' settings.
///
/// This method automatically erases given references.
/// If arguments cannot be passed by reference or, for some reason, you'd like
/// to keep their references, use [`MemoryAPI::Free7()`] method instead.
public function Kill7(
    out AcediaObject objectToRelease1,
    out AcediaObject objectToRelease2,
    out AcediaObject objectToRelease3,
    out AcediaObject objectToRelease4,
    out AcediaObject objectToRelease5,
    out AcediaObject objectToRelease6,
    out AcediaObject objectToRelease7
) {
    Kill(objectToRelease1);
    Kill(objectToRelease2);
    Kill(objectToRelease3);
    Kill(objectToRelease4);
    Kill(objectToRelease5);
    Kill(objectToRelease6);
    Kill(objectToRelease7);
}

/// Releases one reference for each [`AcediaObject`] inside the given array
/// [`objectsToRelease`],
/// calling finalizers for the ones that got all of their references released.
/// Then empties the array.
///
/// Method will attempt to store objects inside [`objectsToRelease`] in their
/// object pools, unless it is forbidden by their class' settings.
///
/// This method automatically forgets contents of the array.
/// If array cannot be passed by reference or, for some reason, you'd like
/// to keep their references, use [`MemoryAPI::FreeMany()`] method instead.
public function KillMany(out array<AcediaObject> objectsToRelease) {
    FreeMany(objectsToRelease);
    objectsToRelease.length = 0;
}

/// Forces engine to perform garbage collection.
///
/// Process of manual garbage collection causes significant lag spike during
/// the game and should be used sparingly and at right moments.
///
/// If no [`LevelCore`] was setup, Acedia doesn't have access to the level and
/// cannot perform garbage collection, meaning that this method can fail.
///
/// By default also cleans up all of the Acedia's objects pools.
/// Set [`keepAcediaPools`] to `true` to NOT garbage collect objects inside
/// pools.
/// Pools won't be dropped regardless of this parameter if no [`LevelCore`] is
/// found.
///
/// Returns `true` if garbage collection successfully happened and `false` if
/// it failed.
/// Garbage collection can only fail if no [`LevelCore`] was yet setup.
public function bool /*unreal*/ CollectGarbage(optional bool keepAcediaPools) {
    local LevelCore core;

    //  Try to find level core
    core = class'ServerLevelCore'.static.GetInstance();
    if (core == none) {
        core = class'ClientLevelCore'.static.GetInstance();
    }
    if (core == none) {
        return false;
    }
    //  Drop content of all `AcediaObjectPools` first
    if (!keepAcediaPools) {
        DropPools();
    }
    //  This makes Unreal Engine do garbage collection
    core.ConsoleCommand("obj garbage");
    return true;
}

/// Registers new object pool to auto-clean before Acedia's garbage collection.
///
/// Returns `true` if [`newPool`] was registered and `false` if
/// `newPool == none` or was already registered.
public function bool RegisterNewPool(AcediaObjectPool newPool) {
    local int i;

    if (newPool == none) {
        return false;
    }
    registeredPools = default.registeredPools;
    for (i = 0; i < registeredPools.length; i += 1) {
        if (registeredPools[i] == newPool) {
            return false;
        }
    }
    registeredPools[registeredPools.length] = newPool;
    default.registeredPools = registeredPools;
    return true;
}

defaultproperties {
}