ff_u_area_gc

Area garbage collector.

Items lying on the ground, dead creatures and body bags are destroyed, with their inventory, once they have been tracked for GC_AREA_DURATION real-time minutes.

An object is tracked from the first scan of its area that finds it. An area is scanned a few seconds after an object enters or leaves it, or an item is lost (e.g. dropped) in it. An item that is picked up is no longer tracked: when it is dropped again, its age starts over. Old objects are looked for about every real-time minute, so an object can outlast its maximum age by up to about a minute and a half.

Variables

ENABLE__FF_U_AREA_GC: (string) (opt-out) Set to "N" on module to disable the unit.

GC_AREA_DURATION: (int) Maximum age, in real-time minutes, before a tracked object is destroyed. Read from the object, its area, then the module. Defaults to 20, at least 1.

Source code

// @code

#include "ff_i_core"

const string ENABLE__FF_U_AREA_GC = "ENABLE__FF_U_AREA_GC";

const string FF_U_AREA_GC__TIMESTAMP = "__FF_U_AREA_GC__TIMESTAMP"; // Time the object started being tracked

const string ARRAY_NAME = "__FF_U_AREA_GC__ARRAY"; // Object array of the tracked objects

const string GC_AREA_DURATION = "GC_AREA_DURATION";

const int DEFAULT = 20; // Real-time minutes

const string FF_U_AREA_GC_FLAG = "__FF_U_AREA_GC_FLAG"; // Delay flag of pending scans and purges


// -----------------------------------------------------------------------------

//! @brief Start tracking oObject, if not already tracked.
//! @param oObject The item.
void _TrackItem(object oObject)
{
  if (GetLocalInt(oObject, FF_U_AREA_GC__TIMESTAMP))
    return;

  LogDebug(__FILE__, "Tracking " + GetObjectInfo(oObject) + " in " + GetObjectInfo(GetArea(oObject)));
  ObjectArrayAdd(ARRAY_NAME, oObject);
  SetLocalInt(oObject, FF_U_AREA_GC__TIMESTAMP, GetTimeStamp());
}


// -----------------------------------------------------------------------------

//! @brief Start tracking oObject for area garbage collection if eligible.
//! @param oObject An item, creature, or BodyBag placeable.
void TrackItem(object oObject);
void TrackItem(object oObject)
{
  if (!GetIsObjectValid(oObject))
    return;

  object oPossessor = GetItemPossessor(oObject);
  int nType = GetObjectType(oObject);

  // Item on the ground
  if (nType == OBJECT_TYPE_ITEM && !GetIsObjectValid(oPossessor))
    _TrackItem(oObject);
  // Dead creature
  else if (nType == OBJECT_TYPE_CREATURE && GetIsDead(oObject))
    _TrackItem(oObject);
  // Body bag
  else if (nType == OBJECT_TYPE_PLACEABLE && (GetTag(oObject) == TAG_BODY_BAG))
    _TrackItem(oObject);
}


// -----------------------------------------------------------------------------

//! @brief Track the eligible objects of oArea, if a scan is pending.
//! @param oArea An area.
void _TrackItems(object oArea)
{
  if (!GetIsObjectValid(oArea))
    return;

  if (!GetDelayFlag(oArea, FF_U_AREA_GC_FLAG))
    return;

  object oItem = GetFirstObjectInArea(oArea, OBJECT_TYPE_ITEM | OBJECT_TYPE_CREATURE | OBJECT_TYPE_PLACEABLE);
  while (GetIsObjectValid(oItem))
  {
    TrackItem(oItem);
    oItem = GetNextObjectInArea(oArea, OBJECT_TYPE_ITEM | OBJECT_TYPE_CREATURE | OBJECT_TYPE_PLACEABLE);
  }
}


// -----------------------------------------------------------------------------

//! @brief Scan oArea for eligible objects in 5 seconds; repeated calls collapse into one.
//! @param oArea An area.
void TrackItems(object oArea);
void TrackItems(object oArea)
{
  // Scan at most every 5 seconds.
  SetDelayFlag(oArea, FF_U_AREA_GC_FLAG);
  DelayCommand(5.0f, _TrackItems(oArea));
}


// -----------------------------------------------------------------------------

//! @brief Remove oObject from area GC tracking.
//! @param oObject A tracked object.
void UntrackItem(object oObject);
void UntrackItem(object oObject)
{
  if (!GetIsObjectValid(oObject))
    return;

  ObjectArrayDelete(ARRAY_NAME, oObject);
  DeleteLocalInt(oObject, FF_U_AREA_GC__TIMESTAMP);
}


// -----------------------------------------------------------------------------

//! @brief Destroy tracked objects older than their maximum age, if a purge is pending.
void _OnTimer();
void _OnTimer()
{
  object oModule = GetModule();
  int nNow = GetTimeStamp();

  if (!GetDelayFlag(oModule, FF_U_AREA_GC_FLAG))
    return;

  sqlquery sqlQuery = ObjectArrayIterate(ARRAY_NAME);
  while (SqlStep(sqlQuery))
  {
    string sUUID = SqlGetString(sqlQuery, 0);
    object oItem = GetObjectByUUID(sUUID);

    if (!GetIsObjectValid(oItem))
    {
      ObjectArrayDelete(ARRAY_NAME, oItem);
      continue;
    }

    int nStart = GetLocalInt(oItem, FF_U_AREA_GC__TIMESTAMP);
    if (nStart == 0)
    {
      TrackItem(oItem);
      continue;
    }

    int nMaxAge = GetRecursiveInt(oItem, GC_AREA_DURATION, DEFAULT);
    nMaxAge = max(nMaxAge, 1);
    nMaxAge = nMaxAge * 60; // Minutes to seconds

    int nSeconds = nNow - nStart;
    if (nSeconds > 0 && nSeconds >= nMaxAge)
    {
      LogInfo(__FILE__ + ": Removal of " + GetObjectInfo(oItem)
        + " (age: " + IntToString(nSeconds)
        + ", max age: " + IntToString(nMaxAge) + ")");
      ObjectArrayDelete(ARRAY_NAME, oItem);

      DestroyInventory(oItem, 0.0f);
      DestroyObject(oItem, 0.01f);
    }
  }
}


// -----------------------------------------------------------------------------

//! @brief Schedule a purge of old objects in 30 seconds; repeated calls collapse into one.
void OnTimer();
void OnTimer()
{
  // Purge at most every 30 seconds.
  SetDelayFlag(GetModule(), FF_U_AREA_GC_FLAG);
  DelayCommand(30.0f, _OnTimer());
}


// -----------------------------------------------------------------------------

void main()
{
  string sEvent = NWNX_Events_GetCurrentEvent();
  if (sEvent == ON_REGISTER)
  {
    if (!GetModuleFlag(ENABLE__FF_U_AREA_GC, TRUE))
      return;

    CreateObjectArray(ARRAY_NAME);

    SubscribeToEvent(ON_DELAYED_REGISTER, __FILE__);
    SubscribeToEvent(ON_REALTIME_MINUTE, __FILE__);
    SubscribeToEvent(ON_DEFAULT_AREA_ENTER, __FILE__);
    SubscribeToEvent(ON_DEFAULT_AREA_EXIT, __FILE__);
  }
  // Avoid the ON_DEFAULT_ITEM_* events during startup.
  else if (sEvent == ON_DELAYED_REGISTER)
  {
    SubscribeToEvent(ON_DEFAULT_ITEM_ACQUIRE, __FILE__);
    SubscribeToEvent(ON_DEFAULT_ITEM_LOST, __FILE__);
  }
  else if (sEvent == ON_DEFAULT_ITEM_ACQUIRE)
    UntrackItem(GetModuleItemAcquired());
  else if (sEvent == ON_DEFAULT_ITEM_LOST)
    TrackItems(GetArea(GetModuleItemLostBy()));
  else if (sEvent == ON_DEFAULT_AREA_ENTER || sEvent == ON_DEFAULT_AREA_EXIT)
  {
    TrackItems(OBJECT_SELF);
    _OnTimer();
  }
  else if (sEvent == ON_REALTIME_MINUTE)
    DelayCommand(GetRandomMaxDelay(MINUTE_MAX_DELAY), OnTimer());
}