Author Topic: [Resource] AI Recycler  (Read 2116 times)

There was and probably still is a problem wherein if you create too many AIConnections, even if you only ever have a few at a time, the server eventually crashes.

A long time ago, I made a solution for this problem but never actually released it. The solution is an AI Recycler. Instead of creating your own AI clients, you ask for one:

Code: (AI Recycler.cs) [Select]
package AIRecycler
{
function GameConnection::onDeath(%cl, %col, %killer, %damageType, %damageArea)
{
if(%cl.getClassName() $= "AIConnection")
{
if(!isObject(AIRecycler))
new SimSet(AIRecycler);
%cl.player.client = "";
if(%cl.recycleOnDeath)
%cl.recycle();
}
Parent::onDeath(%cl, %col, %killer, %damageType, %damageArea);
}
};
activatePackage("AIRecycler");

function CreateAIClient(%persistent)
{
if(AIRecycler.getCount() != 0)
{
%cl = AIRecycler.getObject(0);
%cl.recycleOnDeath = !%persistent;
AIRecycler.remove(%cl);
return %cl;
}
return new AIConnection() { recycleOnDeath = !%persistent; };
}

//.recycle courtesy of Greek2Me
function AiConnection::recycle(%this)
{
if(isObject(%this.minigame))
%this.minigame.removeMember(%this);

if(isObject(%this.camera))
%this.camera.delete();
if(isObject(%this.player))
%this.player.delete();
if(isObject(%this.tempBrick))
%this.tempBrick.delete();
if(isObject(%this.brickGroup) && %this.brickGroup.client == %this)
%this.brickGroup.client = -1;

%index = 0;
while((%field = %this.getTaggedField(%index)) !$= "")
{
//some fields cannot be changed once set.... Thanks, Badspot.
if(%lastField $= %field)
{
%index ++;
continue;
}
%lastField = %field;
%field = getField(%field,0);

//Prevent people from breaking things
if(%field !$= stripChars(%field," `~!@#$%^&*()-=+[{]}\\|;:\'\",<.>/?"))
{
error("ERROR (AiConnection::recycle): Invalid field! Skipping...");
%index ++;
continue;
}

eval(%this @ "." @ %field SPC "= \"\";");
}

if(!isObject(aiRecycler))
{
new SimSet(aiRecycler);
missionCleanup.add(aiRecycler);
}

aiRecycler.add(%this);
}


When you need a new AI client, you call "CreateAIClient();" instead of making your own. If you want the AI client to be persistent (AKA it's able to respawn like a player client without losing its statistics), call "CreateAIClient(1);" instead.
« Last Edit: October 11, 2014, 03:11:05 PM by Xalos »

lovely except i've never coded with AI's :c

Bumping this to post my AiConnection::recycle method. It prepares the bot for reuse by basically stripping it back to a vanilla AiConnection. This is what I use for my similar recycler. Simply call this whenever you would call AiConnection::delete.

Code: [Select]
function AiConnection::recycle(%this)
{
if(isObject(%this.minigame))
%this.minigame.removeMember(%this);

if(isObject(%this.camera))
%this.camera.delete();
if(isObject(%this.player))
%this.player.delete();
if(isObject(%this.tempBrick))
%this.tempBrick.delete();
if(isObject(%this.brickGroup) && %this.brickGroup.client == %this)
%this.brickGroup.client = -1;

%index = 0;
while((%field = %this.getTaggedField(%index)) !$= "")
{
//some fields cannot be changed once set.... Thanks, Badspot.
if(%lastField $= %field)
{
%index ++;
continue;
}
%lastField = %field;
%field = getField(%field,0);

//Prevent people from breaking things
if(%field !$= stripChars(%field," `~!@#$%^&*()-=+[{]}\\|;:\'\",<.>/?"))
{
error("ERROR (AiConnection::recycle): Invalid field! Skipping...");
%index ++;
continue;
}

eval(%this @ "." @ %field SPC "= \"\";");
}

if(!isObject(aiRecycler))
{
new SimSet(aiRecycler);
missionCleanup.add(aiRecycler);
}

aiRecycler.add(%this);
}

Edit: Patched a security flaw reported by Xalos.
« Last Edit: September 24, 2013, 12:25:20 AM by Greek2me »

Sorry for the bump, but

in CreateAIConnection:
Code: [Select]
function CreateAIClient(%persistent)
{
if(AIRecycler.getCount() != 0)
{
AIRecycler.getObject(0);
%cl.persistent = %persistent;
AIRecycler.remove(%cl);
return %cl;
}
return new AIConnection() { persistent = %persistent; };
}
What the forget is %cl?

I had to change AIRecycler.getObject(0); to %cl = AIRecycler.getObject(0); to get any output when I was integrating this into the trains mod.

-snip-

Good catch, thanks. I adapted it from the mod that actually uses it, so things like that slipped through the cracks.

Really, you can add the bot however you would like.

This is my function that takes into account BLIDs. (since they cannot be changed)

Code: [Select]
//Creates an AiConnection and adds it to the minigame.
//The bot is not tied to a specific brick and is instead tied to the minigame.
//@return       objectID      The newly created bot.
function Slayer_MinigameSO::addBotToGame(%this)
{
        //Blockland crashes when ~3500 AiConnection objects have been created.
        //We need to recycle them when possible.
        if(isObject(aiRecycler))
        {
                for(%i = 0; %i < aiRecycler.getCount(); %i ++)
                {
                        %ai = aiRecycler.getObject(%i);
                        if(%ai.bl_id $= %this.creatorBLID || %ai.bl_id $= "")
                        {
                                aiRecycler.remove(%ai);
                                %bot = %ai;
                                break;
                        }
                }
        }

        if(!isObject(%bot))
        {
                %bot = new AiConnection();
        }

        %bot.bl_id = %this.creatorBLID;
        %bot.isHoleBot = true;
        %bot.isSlayerBot = true;

        %this.addMember(%bot);

        Slayer_Support::Debug(2,"Slayer_MinigameSO::addBotToGame",%bot);

        return %bot;
}

Don't add BL_IDs to bots. Even if it works it's just bad practice.

Don't add BL_IDs to bots. Even if it works it's just bad practice.

I see no reason not to. BLIDs are added to bricks, brickgroups, etc.

I see no reason not to. BLIDs are added to bricks, brickgroups, etc.

Neither of those are counterexamples. They're not clients and the BL_ID placed on them is the BL_ID of their owner.
Also, to my mind, saying "et cetera" after only two examples means you don't actually have a third.

.. wow, really? I wrote something almost exactly like this not too long ago - just doesn't have the tagged string removal part.

Code: [Select]
$AIRecycler::Limit = 3400;

function AIConnection::ref() {
if (!isObject(AIRecycler)) {
new SimSet(AIRecycler);
}

if (!isObject(ActiveAIRecycler)) {
new SimSet(ActiveAIRecycler);
}

if (AIRecycler.getCount()) {
%obj = AIRecycler.getObject(0);

AIRecycler.remove(%obj);
ActiveAIRecycler.add(%obj);

return %obj;
}

if (AIRecycler.getCount() + ActiveAIRecycler.getCount() >= $AIRecycler::Limit) {
return -1;
}

%obj = new AIConnection();
ActiveAIRecycler.add(%obj);

return %obj;
}

function AIConnection::unref(%this) {
if (!isObject(AIRecycler)) {
new SimSet(AIRecycler);
}

if (!isObject(ActiveAIRecycler)) {
new SimSet(ActiveAIRecycler);
}

if (isObject(%this.player)) {
%this.player.delete();
%this.player = "";
}

if (isObject(%this.camera)) {
%this.camera.delete();
%this.camera = "";
}

if (isObject(%this.miniGame)) {
// Default mini-game methods do not appreciate AIConnection objects.
// %this.miniGame.removeMember(%this);

for (%i = 0; %i < %this.miniGame.numMembers; %i++) {
if (%this.miniGame.member[%i] == %this) {
%found = true;
%i--;

continue;
}
else if (%found) {
%this.miniGame.member[%i] = %this.miniGame.member[%i + 1];
}
}

if (%found) {
%this.miniGame.numMembers--;
%this.miniGame.member[%this.miniGame.numMembers] = "";
}

%this.miniGame = "";
}

if (isObject(%this.brickGroup)) {
if (%this.brickGroup.client == %this) {
if (%this.brickGroup.doNotDelete) {
%this.brickGroup.client = -1;
}
else {
%this.brickGroup.delete();
}
}

%this.brickGroup = "";
}

if (ActiveAIRecycler.isMember(%this)) {
ActiveAIRecycler.remove(%this);
}

AIRecycler.add(%this);
}
« Last Edit: October 15, 2013, 03:21:08 AM by Port »

Neither of those are counterexamples. They're not clients and the BL_ID placed on them is the BL_ID of their owner.
Also, to my mind, saying "et cetera" after only two examples means you don't actually have a third.
In all honesty I can't remember my reasoning for it anymore.

Code: [Select]
function AiConnection::recycle(%this)
{
if(isObject(%this.minigame))
%this.minigame.removeMember(%this);

//snip
}

It should be noted that this minigame removal will only work if it is not a default minigame - aka Slayer.

.. wow, really? I wrote something almost exactly like this not too long ago - just doesn't have the tagged string removal part.

As long as all of our methods use the aiRecycler object, they should work together nicely.