From c703dec3cf7a114430156dc618f82c4799ae9f94 Mon Sep 17 00:00:00 2001 From: simon Date: Wed, 15 Nov 2017 12:24:01 -0500 Subject: [PATCH] CPU execution time costs energy #16 --- .../src/net/simon987/server/GameServer.java | 11 ++++++- .../simon987/server/assembly/Assembler.java | 2 ++ .../server/assembly/AssemblyResult.java | 10 +++++- .../src/net/simon987/server/assembly/CPU.java | 30 +++++++++++++----- .../net/simon987/server/assembly/Memory.java | 12 +++---- .../server/game/ControllableUnit.java | 4 +++ .../simon987/server/game/GameUniverse.java | 2 +- .../server/webserver/CodeUploadHandler.java | 2 +- config.properties | 4 ++- plugins/Cubot.jar | Bin 21076 -> 24431 bytes 10 files changed, 58 insertions(+), 19 deletions(-) diff --git a/Server/src/net/simon987/server/GameServer.java b/Server/src/net/simon987/server/GameServer.java index c4d2817..8b3ce5b 100644 --- a/Server/src/net/simon987/server/GameServer.java +++ b/Server/src/net/simon987/server/GameServer.java @@ -29,6 +29,8 @@ public class GameServer implements Runnable { private SocketServer socketServer; + private int maxExecutionTime; + public GameServer() { this.config = new ServerConfiguration(new File("config.properties")); @@ -36,6 +38,8 @@ public class GameServer implements Runnable { gameUniverse = new GameUniverse(config); pluginManager = new PluginManager(); + maxExecutionTime = config.getInt("user_timeout"); + //Load all plugins in plugins folder, if it doesn't exist, create it File pluginDir = new File("plugins/"); File[] pluginDirListing = pluginDir.listFiles(); @@ -109,8 +113,13 @@ public class GameServer implements Runnable { if(user.getCpu() != null){ try { + + int timeout = Math.min(user.getControlledUnit().getEnergy(), maxExecutionTime); + user.getCpu().reset(); - user.getCpu().execute(); + int cost = user.getCpu().execute(timeout); + user.getControlledUnit().spendEnergy(cost); + } catch (Exception e) { LogManager.LOGGER.severe("Error executing " + user.getUsername() + "'s code"); e.printStackTrace(); diff --git a/Server/src/net/simon987/server/assembly/Assembler.java b/Server/src/net/simon987/server/assembly/Assembler.java index 96aac4f..034adbb 100755 --- a/Server/src/net/simon987/server/assembly/Assembler.java +++ b/Server/src/net/simon987/server/assembly/Assembler.java @@ -253,6 +253,8 @@ public class Assembler { } else if (tokens[0].toUpperCase().equals(".DATA")) { + LogManager.LOGGER.fine("DEBUG: .data @" + currentLine); + result.defineSegment(Segment.DATA, currentLine, currentOffset); throw new PseudoInstructionException(currentLine); } diff --git a/Server/src/net/simon987/server/assembly/AssemblyResult.java b/Server/src/net/simon987/server/assembly/AssemblyResult.java index 91e6c8b..9cc8f97 100755 --- a/Server/src/net/simon987/server/assembly/AssemblyResult.java +++ b/Server/src/net/simon987/server/assembly/AssemblyResult.java @@ -3,6 +3,7 @@ package net.simon987.server.assembly; import net.simon987.server.ServerConfiguration; import net.simon987.server.assembly.exception.AssemblyException; import net.simon987.server.assembly.exception.DuplicateSegmentException; +import net.simon987.server.logging.LogManager; import java.util.ArrayList; import java.util.HashMap; @@ -29,7 +30,7 @@ public class AssemblyResult { /** * Offset of the code segment */ - private int codeSegmentOffset; + public int codeSegmentOffset; /** * Line of the code segment definition (for editor icons) */ @@ -77,6 +78,10 @@ public class AssemblyResult { if (!codeSegmentSet) { codeSegmentOffset = origin + currentOffset; codeSegmentLine = currentLine; + + LogManager.LOGGER.fine("DEBUG: .text offset @" + codeSegmentOffset); + + codeSegmentSet = true; } else { throw new DuplicateSegmentException(currentLine); @@ -87,6 +92,9 @@ public class AssemblyResult { if (!dataSegmentSet) { dataSegmentOffset = origin + currentOffset; dataSegmentLine = currentLine; + + LogManager.LOGGER.fine("DEBUG: .data offset @" + dataSegmentOffset); + dataSegmentSet = true; } else { throw new DuplicateSegmentException(currentLine); diff --git a/Server/src/net/simon987/server/assembly/CPU.java b/Server/src/net/simon987/server/assembly/CPU.java index 8c50197..65353ad 100755 --- a/Server/src/net/simon987/server/assembly/CPU.java +++ b/Server/src/net/simon987/server/assembly/CPU.java @@ -60,10 +60,11 @@ public class CPU implements JSONSerialisable{ private ServerConfiguration config; - private long timeout; - private int registerSetSize; + private static final char EXECUTION_COST_ADDR = 0x0300; + private static final char EXECUTED_INS_ADDR = 0x0301; + /** * Creates a new CPU */ @@ -74,8 +75,6 @@ public class CPU implements JSONSerialisable{ attachedHardware = new HashMap<>(); codeSegmentOffset = config.getInt("org_offset"); - timeout = config.getInt("user_timeout"); - instructionSet.add(new JmpInstruction(this)); instructionSet.add(new JnzInstruction(this)); instructionSet.add(new JzInstruction(this)); @@ -116,7 +115,7 @@ public class CPU implements JSONSerialisable{ ip = codeSegmentOffset; } - public void execute() { + public int execute(int timeout) { long startTime = System.currentTimeMillis(); int counter = 0; @@ -128,10 +127,16 @@ public class CPU implements JSONSerialisable{ while (!status.isBreakFlag()) { counter++; - if(counter % 1000 == 0){ + if (counter % 10000 == 0) { if (System.currentTimeMillis() >= (startTime + timeout)) { LogManager.LOGGER.fine("CPU Timeout " + this + " after " + counter + "instructions (" + timeout + "ms): " + (double) counter / ((double) timeout / 1000) / 1000000 + "MHz"); - return; + + //Write execution cost and instruction count to memory + memory.set(EXECUTION_COST_ADDR, timeout); + memory.set(EXECUTED_INS_ADDR, Util.getHigherWord(counter)); + memory.set(EXECUTED_INS_ADDR, Util.getLowerWord(counter)); + + return timeout; } } @@ -151,8 +156,17 @@ public class CPU implements JSONSerialisable{ executeInstruction(instruction, source, destination); // LogManager.LOGGER.info(instruction.getMnemonic()); } - double elapsed = (System.currentTimeMillis() - startTime); + int elapsed = (int) (System.currentTimeMillis() - startTime); + LogManager.LOGGER.fine(counter + " instruction in " + elapsed + "ms : " + (double) counter / (elapsed / 1000) / 1000000 + "MHz"); + + + //Write execution cost and instruction count to memory + memory.set(EXECUTION_COST_ADDR, elapsed); + memory.set(EXECUTED_INS_ADDR, Util.getHigherWord(counter)); + memory.set(EXECUTED_INS_ADDR, Util.getLowerWord(counter)); + + return elapsed; } public void executeInstruction(Instruction instruction, int source, int destination) { diff --git a/Server/src/net/simon987/server/assembly/Memory.java b/Server/src/net/simon987/server/assembly/Memory.java index a77bf94..ef12420 100755 --- a/Server/src/net/simon987/server/assembly/Memory.java +++ b/Server/src/net/simon987/server/assembly/Memory.java @@ -42,14 +42,14 @@ public class Memory implements Target, JSONSerialisable { */ @Override public int get(int address) { - address = (char)address * 2; //Because our Memory is only divisible by 16bits + address = address * 2; //Because our Memory is only divisible by 16bits - if (address < 0 || address + 2 > bytes.length) { + if (address + 2 > bytes.length) { LogManager.LOGGER.info("DEBUG: Trying to get memory out of bounds " + address); return 0; } - return (((bytes[address] & 0xFF) << 8) | (bytes[address + 1] & 0xFF)); + return ((bytes[address] & 0xFF) << 8) | (bytes[address + 1] & 0xFF); } /** @@ -60,7 +60,7 @@ public class Memory implements Target, JSONSerialisable { offset = (char)offset * 2; - if (offset + count > this.bytes.length || count < 0 || offset < 0 || srcOffset >= bytes.length) { + if (offset + count > this.bytes.length || srcOffset >= bytes.length || count < 0 || offset < 0) { return false; } @@ -80,12 +80,12 @@ public class Memory implements Target, JSONSerialisable { address = (char)address * 2; - if (address < 0 || address + 2 > bytes.length) { + if (address + 2 > bytes.length) { LogManager.LOGGER.info("DEBUG: Trying to set memory out of bounds: " + address); return; } - bytes[address] = (byte) ((value >> 8) & 0xFF); + bytes[address] = (byte) ((value >> 8)); bytes[address + 1] = (byte) (value & 0xFF); } diff --git a/Server/src/net/simon987/server/game/ControllableUnit.java b/Server/src/net/simon987/server/game/ControllableUnit.java index 2a4c714..296cc65 100644 --- a/Server/src/net/simon987/server/game/ControllableUnit.java +++ b/Server/src/net/simon987/server/game/ControllableUnit.java @@ -17,4 +17,8 @@ public interface ControllableUnit { Memory getFloppyData(); + boolean spendEnergy(int energy); + + int getEnergy(); + } diff --git a/Server/src/net/simon987/server/game/GameUniverse.java b/Server/src/net/simon987/server/game/GameUniverse.java index 0007246..bf5c39f 100644 --- a/Server/src/net/simon987/server/game/GameUniverse.java +++ b/Server/src/net/simon987/server/game/GameUniverse.java @@ -114,7 +114,7 @@ public class GameUniverse implements JSONSerialisable{ //Write assembled code to mem user.getCpu().getMemory().write((short) ar.origin, ar.bytes, 0, ar.bytes.length); - user.getCpu().setCodeSegmentOffset(ar.origin); + user.getCpu().setCodeSegmentOffset(ar.codeSegmentOffset); //Init diff --git a/Server/src/net/simon987/server/webserver/CodeUploadHandler.java b/Server/src/net/simon987/server/webserver/CodeUploadHandler.java index 9aade12..8cec247 100644 --- a/Server/src/net/simon987/server/webserver/CodeUploadHandler.java +++ b/Server/src/net/simon987/server/webserver/CodeUploadHandler.java @@ -29,7 +29,7 @@ public class CodeUploadHandler implements MessageHandler { //Write assembled code to mem user.getUser().getCpu().getMemory().write((char) ar.origin, ar.bytes, 0, ar.bytes.length); - user.getUser().getCpu().setCodeSegmentOffset(ar.origin); + user.getUser().getCpu().setCodeSegmentOffset(ar.codeSegmentOffset); JSONObject response = new JSONObject(); response.put("t", "codeResponse"); diff --git a/config.properties b/config.properties index a1eac5a..65e87bc 100644 --- a/config.properties +++ b/config.properties @@ -60,4 +60,6 @@ wg_minCopperCount=0 wg_maxCopperCount=2 # ---------------------------------------------- # Maximum execution time of user code in ms -user_timeout=40 \ No newline at end of file +user_timeout=500 +# Free CPU execution time in ms +user_free_execution_time=2 \ No newline at end of file diff --git a/plugins/Cubot.jar b/plugins/Cubot.jar index 7991cf6957c090a58d5575b78bed407a07371135..f574edc225de05048582658e986c587e9c54c019 100644 GIT binary patch delta 8220 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

Jw1d0f-LWCyI$Hm-RuX^CR`V$DB#Sn% z#@Eu#ke{dEi?{ODFJBZW%lD&A#2oOHel(A?k~N2laAiKP?spIMIc@>*7x zlPZ(U9iOPN6-N-3ow~Y1meX7l{QZ5@jU(SKET$bocz2AjpW~5Zc%g;vsgH8>(|Sud zSvLI`$rv4Xilds19oT!5`?g&=d-Pz)Ov|aZry_GtTsKC)JgCbhGcp008{r)ob1f7e zz~>GX=qhrdIvv5wkgQ2R<4#51v`Eeu$MxfmgM<`w7ehF8?#SvWGwZP zwJOYJbwI|OKo@&l@Pr7Fc$8>|OJF}_TOMY`Aq9D}ZTvWx0pIyvGG~Ks#C3GSTHf2W zdeg}eVl>FXI)j|Hh55?@WNA2=g%5^DvJ69Wa^IbLdcj3LOj6f1KUaM{)I)El_k)=c zj7seUV^+ZmZ`@k#O7J7uS|(+Fsz{s%6&j?w6ak|n zHE_lmJX@Yuetk`3iI@-2Lr`0kZ!yVKZ4%)nZ3`|mnfC5b|JcGp({&BNc1KQ`)d4{f zW){jU*&;FtP9i5m4Mlzzi?v&lGPRp%c>LQyJdBI_DDXvnPiiLjm(REfSuSX`t`aee zBAtmK>W!Z4DtJqvh#SbD*PMD|P?~yUzy|MpD>yVzXw<~GMX1hxV#N6lz`%8rQf)RwmL2T1qB{f~K7~0#DK^2;+VVZ=!c*Q_D5C7 zAG%HlJtjUtGYHZ5)hSC}~UKo_>v_;b!akmK&qM$BE9(Ib|< z_Gzco&}8AYahD+)?U*UrdSi||dZ^(u%N*&^R}@EFk1t-O*RKM&@3>+w+zYIUvIx<1 zQGW*YMw-OO5pox%Z*B|S6VIf>r_)nDcgBAXduGLOmJ==4J#VqoHtZ=2Eg#bar= zR;Zp4);y4dh8iW>!*e#h>?ac5&P%REiE`Qa?h#X@^c!=#c_z}L!K7K2hp{wl4Ofhd zb^ATfd)&#}LC>HMsJs~7>`sG*7+To|+Z<)dM z$7zuHSAC|m>G_FC`$0KzDxa(EOa_jl>LQn%*`<8888t1|6nIWp(DCcfRhMsfPA(Tu zzTci)@ShCSHr`3s5RtVS=e*Nq*1)NcGk@0*+;5%(=PvKU840Vi~ zauc!Wu~WNns_?i0U&~eKGP5a;KFae$Ik8!%_EAn0DQLB7YJL=A*&B6KxkGreT26iu zelOmvJYxqJxc|oDIe8X!Z!a>Z2YyZKl=i8NpW5ss&GX&lg+~)3wUqgUhOAJ%w-ZLmY zMGgC%XXk=7nxz=0DRE%V!*4Ud0?hfvYfO&_94?t!23ASEjVhBa?CIN)c;axyx3juN zH5Tp=D6e;>*~RZmkfHFaI}nG7_o&6!tF2~$q`SG78c6Ma2s2kJ45+*5O;x8(hg&_H z%lI0}Wtz%u#jwOb z2E)GwCKv-FHP2ta|1Q@{`ozVCV%hvAFWVmu>b%7 delta 4990 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 O|BPt@005@@H~K%Nk!G9#