package org.groovymud.engine;
/* Copyright 2008 Matthew Corby-Eaglen
*
* 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.
*/
import java.net.MalformedURLException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import net.wimpi.telnetd.TelnetD;
import org.apache.log4j.Logger;
import org.groovymud.engine.event.HeartBeatListener;
import org.groovymud.object.MudObject;
import org.groovymud.object.alive.Player;
import org.groovymud.object.registry.MudObjectAttendant;
import org.groovymud.object.registry.Registry;
import org.groovymud.object.room.Room;
import org.groovymud.shell.telnetd.LoginShell;
import org.groovymud.shell.telnetd.ShellBridge;
import org.groovymud.utils.CountingMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* The mud engine is responsible for starting up the mud, shutting it down,
* distributing heartbeats, resetting rooms and registering player states.
*
* @author matt
*
*/
public abstract class JMudEngine extends Thread {
public static int heartBeatsUntilAutosave = 60;
public static int heartBeatsUntilReset = 1200;
public static String netDeadStatus = "net dead";
protected static int mudHeartbeatLength = 1000;
private static TelnetD telnetDaemon;
private final Registry objectRegistry;
private volatile boolean running;
private final static Logger logger = Logger.getLogger(JMudEngine.class);
private static AbstractApplicationContext context;
private final MudObjectAttendant objectAttendant;
private final CountingMap<Player> netDeadPlayers;
private static boolean shutdownRequested = false;
private final ExecutorService executor;
@Autowired(required = true)
public JMudEngine(Registry registry, MudObjectAttendant attendant, CountingMap<Player> netDeadMap, ExecutorService exec, TelnetD daemon) {
running = true;
this.objectRegistry = registry;
this.objectAttendant = attendant;
this.netDeadPlayers = netDeadMap;
this.executor = exec;
telnetDaemon = daemon;
}
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, InstantiationException, IllegalAccessException {
System.out.println("workspace.loc:" + System.getProperty("workspace.loc"));
System.out.println("lib:" + System.getProperty("lib"));
System.out.println("mudspace:" + System.getProperty("mudspace"));
context = new ClassPathXmlApplicationContext(new String[] { "applicationContext.xml", "sshdContext.xml", "telnetdContext.xml", "/commandAliasContext.xml" }, JMudEngine.class);
context.registerShutdownHook();
ShellBridge.setApplicationContext(context);
final JMudEngine engine = (JMudEngine) context.getBean("mudEngine");
engine.getExecutor().execute(engine);
telnetDaemon.start();
}
public void run() {
logger.info("GroovyMud up and running");
int saveTime = 0;
int resetTime = 0;
while (isRunning()) {
try {
doHeartBeat();
handleTheNetDead();
checkPlayerHandles();
if (saveTime++ == heartBeatsUntilAutosave) {
savePlayers();
saveTime = 0;
}
if (resetTime++ == heartBeatsUntilReset) {
resetRooms();
resetTime = 0;
}
if (isShutdownRequested()) {
createShutdownBehaviour().handleShutdown();
}
Thread.sleep(mudHeartbeatLength);
} catch (InterruptedException e) {
logger.error(e, e);
}
}
}
protected void resetRooms() {
logger.info("Resetting rooms");
Iterator<MudObject> i = getObjectRegistry().getMudObjects().iterator();
while (i.hasNext()) {
MudObject o = i.next();
if (o instanceof Room) {
((Room) o).initialise();
}
Thread.yield();
}
}
protected void savePlayers() {
Iterator<LoginShell> i = getObjectRegistry().getActivePlayerHandles().iterator();
if (i.hasNext()) {
logger.info("auto saving..");
}
while (i.hasNext()) {
LoginShell o = i.next();
Player p = o.getPlayer();
if (getObjectRegistry().getMudObject(p.getName()) == null) {
continue;
}
getObjectAttendant().savePlayerData(p);
p = null;
}
}
protected void checkPlayerHandles() {
CountingMap<Player> netDead = getNetDeadPlayers();
Set<Player> totallyDead = netDead.getKeysAbove(30);
for (Player p : totallyDead) {
p.removeStatus(netDeadStatus);
getObjectRegistry().dest(p, false);
getNetDeadPlayers().remove(p);
p = null;
}
}
protected void doHeartBeat() {
Set<MudObject> objects = getObjectRegistry().getMudObjects();
Iterator<MudObject> i = objects.iterator();
while (i.hasNext()) {
MudObject o = i.next();
if (o instanceof HeartBeatListener) {
try {
((HeartBeatListener) o).heartBeat();
} catch (Exception e) {
// ^^ ick but we don't want to break the whole hearbeat
// system because of
// one object's error now do we?
logger.error(e, e);
}
}
Thread.yield();
}
}
protected void handleTheNetDead() {
Iterator<LoginShell> x = new HashSet<LoginShell>(getObjectRegistry().getActivePlayerHandles()).iterator();
CountingMap<Player> netDead = getNetDeadPlayers();
while (x.hasNext()) {
LoginShell shell = x.next();
if (!shell.getConnection().isActive()) {
Player p = getObjectRegistry().getPlayerByHandle(shell);
if (!netDead.containsKey(p)) {
logger.info(p.getName() + " is net dead..");
p.addStatus(netDeadStatus);
}
netDead.increment(p);
p = null;
}
}
}
public boolean isRunning() {
return running;
}
public void setRunning(boolean runningArg) {
this.running = runningArg;
}
public static void requestShutDown(Player requester) {
logger.info("shutdown requested!!!");
shutdownRequested = true;
}
public static AbstractApplicationContext getContext() {
return context;
}
public Registry getObjectRegistry() {
return objectRegistry;
}
public MudObjectAttendant getObjectAttendant() {
return objectAttendant;
}
public TelnetD getTelnetDaemon() {
return telnetDaemon;
}
public void setTelnetDaemon(TelnetD telnetDeamon) {
JMudEngine.telnetDaemon = telnetDeamon;
}
public CountingMap<Player> getNetDeadPlayers() {
return netDeadPlayers;
}
public void setShutdownRequested(boolean shutdownRequest) {
this.shutdownRequested = shutdownRequest;
}
public boolean isShutdownRequested() {
return shutdownRequested;
}
protected void shutdownNow() {
logger.info("shutdownNow called");
destAllPlayers();
getTelnetDaemon().stop();
this.setRunning(false);
logger.info("shudownNow complete");
System.exit(0);
}
protected void destAllPlayers() {
Iterator<LoginShell> x = new HashSet<LoginShell>(getObjectRegistry().getActivePlayerHandles()).iterator();
while (x.hasNext()) {
LoginShell shell = x.next();
if (shell.getConnection().isActive()) {
Player p = getObjectRegistry().getPlayerByHandle(shell);
getObjectRegistry().dest(p, false);
}
}
}
public abstract ShutdownBehaviour createShutdownBehaviour();
public ExecutorService getExecutor() {
return executor;
}
public int getHeartBeatsUntilAutosave() {
return heartBeatsUntilAutosave;
}
public static void setHeartBeatsUntilAutosave(int heartBeats) {
JMudEngine.heartBeatsUntilAutosave = heartBeats;
}
public int getHeartBeatsUntilReset() {
return heartBeatsUntilReset;
}
public void setHeartBeatsUntilReset(int heartBeats) {
JMudEngine.heartBeatsUntilReset = heartBeats;
}
public String getNetDeadStatus() {
return netDeadStatus;
}
public void setNetDeadStatus(String status) {
JMudEngine.netDeadStatus = status;
}
public int getMudHeartbeatLength() {
return mudHeartbeatLength;
}
public void setMudHeartbeatLength(int length) {
JMudEngine.mudHeartbeatLength = length;
}
}