package com.planet_ink.coffee_mud.core.threads;
import com.planet_ink.coffee_mud.Libraries.interfaces.*;
import com.planet_ink.coffee_mud.core.database.DBConnection;
import com.planet_ink.coffee_mud.core.interfaces.*;
import com.planet_ink.coffee_mud.core.*;
import com.planet_ink.coffee_mud.Abilities.interfaces.*;
import com.planet_ink.coffee_mud.Areas.interfaces.*;
import com.planet_ink.coffee_mud.Behaviors.interfaces.*;
import com.planet_ink.coffee_mud.CharClasses.interfaces.*;
import com.planet_ink.coffee_mud.Commands.interfaces.*;
import com.planet_ink.coffee_mud.Common.interfaces.*;
import com.planet_ink.coffee_mud.Exits.interfaces.*;
import com.planet_ink.coffee_mud.Items.interfaces.*;
import com.planet_ink.coffee_mud.Locales.interfaces.*;
import com.planet_ink.coffee_mud.MOBS.interfaces.*;
import com.planet_ink.coffee_mud.Races.interfaces.*;
import java.util.*;
/*
Copyright 2000-2006 Bo Zimmerman
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
public class UtiliThread extends Thread
{
public boolean started=false;
private boolean shutDown=false;
public long lastStart=0;
public long lastStop=0;
public static long milliTotal=0;
public static long tickTotal=0;
public static String status="";
private static boolean debugging=false;
private static UtiliThread inst=new UtiliThread();
public static UtiliThread instance(){return inst;}
public UtiliThread()
{
super("UtiliThread");
setName("UtiliThread");
setDaemon(true);
}
private void status(String s)
{
status=s;
if(debugging) Log.debugOut("SaveThread",s);
}
public void vacuum()
{
boolean corpsesOnly=CMSecurity.isSaveFlag("ROOMITEMS");
boolean noMobs=CMSecurity.isSaveFlag("ROOMMOBS");
status("expiration sweep");
long currentTime=System.currentTimeMillis();
boolean debug=CMSecurity.isDebugging("VACUUM");
try
{
Vector stuffToGo=new Vector();
Item I=null;
MOB M=null;
Room R=null;
Vector roomsToGo=new Vector();
MOB expireM=CMLib.map().god(null);
CMMsg expireMsg=CMClass.getMsg(expireM,R,null,CMMsg.MSG_EXPIRE,null);
for(Enumeration r=CMLib.map().rooms();r.hasMoreElements();)
{
R=(Room)r.nextElement();
expireM.setLocation(R);
expireMsg.setTarget(R);
if((R.expirationDate()!=0)
&&(currentTime>R.expirationDate())
&&(R.okMessage(R,expireMsg)))
roomsToGo.addElement(R);
else
if(!R.amDestroyed())
{
stuffToGo.clear();
for(int i=0;i<R.numItems();i++)
{
I=R.fetchItem(i);
if((I!=null)
&&((!corpsesOnly)||(I instanceof DeadBody))
&&(I.expirationDate()!=0)
&&(I.owner()==R)
&&(currentTime>I.expirationDate()))
stuffToGo.add(I);
}
for(int i=0;i<R.numInhabitants();i++)
{
M=R.fetchInhabitant(i);
if((M!=null)
&&(!noMobs)
&&(M.expirationDate()!=0)
&&(currentTime>M.expirationDate()))
stuffToGo.add(M);
}
}
if(stuffToGo.size()>0)
{
boolean success=true;
for(int s=0;s<stuffToGo.size();s++)
{
Environmental E=(Environmental)stuffToGo.elementAt(s);
status("expiring "+E.Name());
expireMsg.setTarget(E);
if(R.okMessage(expireM,expireMsg))
R.sendOthers(expireM,expireMsg);
else
success=false;
if(debug) Log.sysOut("UTILITHREAD","Expired "+E.Name()+" in "+CMLib.map().getExtendedRoomID(R)+": "+success);
}
stuffToGo.clear();
}
}
for(int r=0;r<roomsToGo.size();r++)
{
R=(Room)roomsToGo.elementAt(r);
expireM.setLocation(R);
expireMsg.setTarget(R);
status("expirating room "+CMLib.map().getExtendedRoomID(R));
if(debug)
{
String roomID=CMLib.map().getExtendedRoomID(R);
if(roomID.length()==0) roomID="(unassigned grid room, probably in the air)";
if(debug) Log.sysOut("UTILITHREAD","Expired "+roomID+".");
}
R.sendOthers(expireM,expireMsg);
}
}
catch(java.util.NoSuchElementException e){}
}
public void insertOrderDeathInOrder(DVector DV, long lastStart, String msg, Tick tock)
{
if(DV.size()>0)
for(int i=0;i<DV.size();i++)
{
if(((Long)DV.elementAt(i,1)).longValue()>lastStart)
{
DV.insertElementAt(i,new Long(lastStart),msg,tock);
return;
}
}
DV.addElement(new Long(lastStart),msg,tock);
}
public void debugDumpStack(Thread theThread)
{
// I wish Java had compiler directives. Would be great to un-comment this for 1.5 JVMs
//java.lang.StackTraceElement[] s=(java.lang.StackTraceElement[])Thread.getAllStackTraces().get(theThread);
//for(int i=0;i<s.length;i++)
// Log.debugOut("UtiliDump"," "+s[i].getClassName()+": "+s[i].getMethodName()+"("+s[i].getFileName()+": "+s[i].getLineNumber()+")");
}
public void checkHealth()
{
long lastDateTime=System.currentTimeMillis()-(5*TimeManager.MILI_MINUTE);
long longerDateTime=System.currentTimeMillis()-(120*TimeManager.MILI_MINUTE);
status("checking");
try
{
for(Enumeration r=CMLib.map().rooms();r.hasMoreElements();)
{
Room R=(Room)r.nextElement();
for(int m=0;m<R.numInhabitants();m++)
{
MOB mob=R.fetchInhabitant(m);
if((mob!=null)&&(mob.lastTickedDateTime()>0)&&(mob.lastTickedDateTime()<lastDateTime))
{
boolean ticked=CMLib.threads().isTicking(mob,Tickable.TICKID_MOB);
boolean isDead=mob.amDead();
String wasFrom=((mob.getStartRoom()!=null)?mob.getStartRoom().roomID():"NULL");
if(!ticked)
{
if(CMLib.map().getPlayer(mob.Name())==null)
Log.errOut("UtiliThread",mob.name()+" in room "+R.roomID()+" unticked (is ticking="+(ticked)+", dead="+isDead+", Home="+wasFrom+") since: "+CMLib.time().date2String(mob.lastTickedDateTime())+"."+(ticked?"":" This mob has been destroyed. May he rest in peace."));
else
Log.errOut("UtiliThread","Player "+mob.name()+" in room "+R.roomID()+" unticked (is ticking="+(ticked)+", dead="+isDead+", Home="+wasFrom+") since: "+CMLib.time().date2String(mob.lastTickedDateTime())+"."+(ticked?"":" This mob has been put aside."));
status("destroying unticked mob "+mob.name());
if(CMLib.map().getPlayer(mob.Name())==null) mob.destroy();
R.delInhabitant(mob);
status("checking");
}
}
}
}
}
catch(java.util.NoSuchElementException e){}
status("checking tick groups.");
DVector orderedDeaths=new DVector(3);
try
{
for(Enumeration v=CMLib.threads().tickGroups();v.hasMoreElements();)
{
Tick almostTock=(Tick)v.nextElement();
if((almostTock.awake)
&&(almostTock.lastStop<lastDateTime))
{
TockClient client=almostTock.lastClient;
if(client==null)
insertOrderDeathInOrder(orderedDeaths,0,"LOCKED GROUP "+almostTock.getCounter()+"! No further information.",almostTock);
else
if((!CMath.bset(client.tickID,Tickable.TICKID_LONGERMASK))||(almostTock.lastStop<longerDateTime))
{
if(client.clientObject==null)
insertOrderDeathInOrder(orderedDeaths,0,"LOCKED GROUP "+almostTock.getCounter()+": NULL @"+CMLib.time().date2String(client.lastStart)+", tickID "+client.tickID,almostTock);
else
{
StringBuffer str=null;
Tickable obj=client.clientObject;
long code=client.clientObject.getTickStatus();
String codeWord=CMLib.threads().getTickStatusSummary(client.clientObject);
String msg=null;
if(obj instanceof Environmental)
str=new StringBuffer("LOCKED GROUP "+almostTock.getCounter()+" : "+obj.name()+" ("+((Environmental)obj).ID()+") @"+CMLib.time().date2String(client.lastStart)+", status("+code+" ("+codeWord+"), tickID "+client.tickID);
else
str=new StringBuffer("LOCKED GROUP "+almostTock.getCounter()+": "+obj.name()+", status("+code+" ("+codeWord+") @"+CMLib.time().date2String(client.lastStart)+", tickID "+client.tickID);
if((obj instanceof MOB)&&(((MOB)obj).location()!=null))
msg=str.toString()+" in "+((MOB)obj).location().roomID();
else
if((obj instanceof Item)&&(((Item)obj).owner()!=null)&&(((Item)obj).owner() instanceof Room))
msg=str.toString()+" in "+((Room)((Item)obj).owner()).roomID();
else
if((obj instanceof Item)&&(((Item)obj).owner()!=null)&&(((Item)obj).owner() instanceof MOB))
msg=str.toString()+" owned by "+((MOB)((Item)obj).owner()).name();
else
if(obj instanceof Room)
msg=str.toString()+" is "+((Room)obj).roomID();
else
msg=str.toString();
insertOrderDeathInOrder(orderedDeaths,client.lastStart,msg,almostTock);
}
}
debugDumpStack(almostTock);
}
}
}
catch(java.util.NoSuchElementException e){}
for(int i=0;i<orderedDeaths.size();i++)
Log.errOut("UtiliThread",(String)orderedDeaths.elementAt(i,2));
status("killing tick groups.");
for(int x=0;x<orderedDeaths.size();x++)
{
Tick almostTock=(Tick)orderedDeaths.elementAt(x,3);
Vector objs=new Vector();
try{
for(Iterator e=almostTock.tickers();e.hasNext();)
objs.addElement(e.next());
}catch(NoSuchElementException e){}
almostTock.shutdown();
if(CMLib.threads() instanceof ServiceEngine)
((ServiceEngine)CMLib.threads()).delTickGroup(almostTock);
for(int i=0;i<objs.size();i++)
{
TockClient c=(TockClient)objs.elementAt(i);
CMLib.threads().startTickDown(c.clientObject,c.tickID,c.reTickDown);
}
}
status("checking player titles.");
for(Enumeration e=CMLib.map().players();e.hasMoreElements();)
{
MOB M=(MOB)e.nextElement();
if(M.playerStats()!=null)
{
if((CMLib.login().evaluateAutoTitles(M))&&(!CMLib.flags().isInTheGame(M,true)))
CMLib.database().DBUpdatePlayerStatsOnly(M);
}
}
status("checking player sessions.");
for(int s=0;s<CMLib.sessions().size();s++)
{
Session S=CMLib.sessions().elementAt(s);
long time=System.currentTimeMillis()-S.lastLoopTime();
if(time>0)
{
if((S.mob()!=null))
{
long check=60000;
if((S.previousCMD()!=null)
&&(S.previousCMD().size()>0)
&&(((String)S.previousCMD().firstElement()).equalsIgnoreCase("IMPORT")
||((String)S.previousCMD().firstElement()).equalsIgnoreCase("EXPORT")
||((String)S.previousCMD().firstElement()).equalsIgnoreCase("MERGE")))
check=check*60;
else
if(CMSecurity.isAllowed(S.mob(),S.mob().location(),"CMDROOMS"))
check=check*15;
else
if(S.getStatus()==Session.STATUS_LOGIN)
check=check*5;
if(time>(check*10))
{
String roomID=S.mob()!=null?CMLib.map().getExtendedRoomID(S.mob().location()):"";
if((S.previousCMD()==null)||(S.previousCMD().size()==0))
Log.errOut("UtiliThread","Kicking out: "+((S.mob()==null)?"Unknown":S.mob().Name())+" who has spent "+time+" millis in creation (probably).");
else
{
Log.errOut("UtiliThread","KILLING DEAD Session: "+((S.mob()==null)?"Unknown":S.mob().Name())+" ("+roomID+"), out for "+time);
Log.errOut("UtiliThread","STATUS was :"+S.getStatus()+", LASTCMD was :"+((S.previousCMD()!=null)?S.previousCMD().toString():""));
}
if(S instanceof Thread)
debugDumpStack((Thread)S);
status("killing session ");
CMLib.sessions().stopSessionAtAllCosts(S);
status("checking player sessions.");
}
else
if(time>check)
{
if((S.mob()==null)||(S.mob().Name()==null)||(S.mob().Name().length()==0))
CMLib.sessions().stopSessionAtAllCosts(S);
else
if((S.previousCMD()!=null)&&(S.previousCMD().size()>0))
{
String roomID=S.mob()!=null?CMLib.map().getExtendedRoomID(S.mob().location()):"";
if((S.isLockedUpWriting())
&&(CMLib.flags().isInTheGame(S.mob(),true)))
{
Log.errOut("UtiliThread","LOGGED OFF Session: "+((S.mob()==null)?"Unknown":S.mob().Name())+" ("+roomID+"), out for "+time+": "+S.isLockedUpWriting());
CMLib.sessions().stopSessionAtAllCosts(S);
}
else
Log.errOut("UtiliThread","Suspect Session: "+((S.mob()==null)?"Unknown":S.mob().Name())+" ("+roomID+"), out for "+time);
if((S.getStatus()!=1)||((S.previousCMD()!=null)&&(S.previousCMD().size()>0)))
Log.errOut("UtiliThread","STATUS is :"+S.getStatus()+", LASTCMD was :"+((S.previousCMD()!=null)?S.previousCMD().toString():""));
else
Log.errOut("UtiliThread","STATUS is :"+S.getStatus()+", no last command available.");
}
}
}
else
if(time>(60000))
{
String roomID=S.mob()!=null?CMLib.map().getExtendedRoomID(S.mob().location()):"";
Log.errOut("UtiliThread","KILLING DEAD Session: "+((S.mob()==null)?"Unknown":S.mob().Name())+" ("+roomID+"), out for "+time);
if((S.getStatus()!=1)||((S.previousCMD()!=null)&&(S.previousCMD().size()>0)))
Log.errOut("UtiliThread","STATUS was :"+S.getStatus()+", LASTCMD was :"+((S.previousCMD()!=null)?S.previousCMD().toString():""));
status("killing session ");
if(S instanceof Thread)
debugDumpStack((Thread)S);
CMLib.sessions().stopSessionAtAllCosts(S);
status("checking player sessions");
}
}
}
}
public void shutdown()
{
shutDown=true;
CMLib.killThread(this,500,1);
started=false;
}
public void forceTick()
{
if(status.equalsIgnoreCase("sleeping"))
{
interrupt();
return;
}
}
public void run()
{
lastStart=System.currentTimeMillis();
if(started)
{
Log.errOut("UtiliThread","DUPLICATE UTILITHREAD RUNNING!!");
return;
}
started=true;
shutDown=false;
// now start the thread
while(!CMProps.getBoolVar(CMProps.SYSTEMB_MUDSTARTED))
{
try{Thread.sleep(2000);}catch(Exception e){}
}
try{Thread.sleep(Tickable.TIME_TICK*2);}catch(Exception e){started=false;}
while(started)
{
try
{
while(CMLib.threads().isAllSuspended())
try{Thread.sleep(2000);}catch(Exception e){}
if(!CMSecurity.isDisabled("UTILITHREAD"))
{
debugging=CMSecurity.isDebugging("UTILITHREAD");
vacuum();
checkHealth();
Resources.removeResource("SYSTEM_HASHED_MASKS");
lastStop=System.currentTimeMillis();
milliTotal+=(lastStop-lastStart);
tickTotal++;
status("sleeping");
Thread.sleep(MudHost.TIME_UTILTHREAD_SLEEP);
lastStart=System.currentTimeMillis();
}
else
{
status("sleeping");
Thread.sleep(MudHost.TIME_UTILTHREAD_SLEEP);
}
}
catch(InterruptedException ioe)
{
Log.sysOut("UtiliThread","Interrupted!");
if(shutDown)
{
shutDown=false;
break;
}
}
catch(Exception e)
{
Log.errOut("UtiliThread",e);
}
}
// force final time tick!
Vector timeObjects=new Vector();
for(Enumeration e=CMLib.map().areas();e.hasMoreElements();)
{
Area A=((Area)e.nextElement());
if(!timeObjects.contains(A.getTimeObj()))
timeObjects.addElement(A.getTimeObj());
}
for(int t=0;t<timeObjects.size();t++)
((TimeClock)timeObjects.elementAt(t)).save();
Log.sysOut("UtiliThread","Shutdown complete.");
}
}