From 460558bc87c197002961fdf1ac923730052d5c7c Mon Sep 17 00:00:00 2001 From: Anuken Date: Wed, 7 Feb 2018 14:47:39 -0500 Subject: [PATCH] Implemented toggling of multithreading --- .../io/anuke/mindustry/AndroidLauncher.java | 7 + core/assets/bundles/bundle.properties | 1 + core/src/io/anuke/mindustry/Mindustry.java | 52 +------ core/src/io/anuke/mindustry/Vars.java | 2 + core/src/io/anuke/mindustry/core/Control.java | 7 + core/src/io/anuke/mindustry/core/Logic.java | 6 +- .../anuke/mindustry/core/ThreadHandler.java | 82 +++++++++++ .../anuke/mindustry/entities/TileEntity.java | 4 +- core/src/io/anuke/mindustry/io/Platform.java | 24 ++++ .../ui/dialogs/SettingsMenuDialog.java | 9 ++ .../blocks/types/distribution/Conveyor.java | 127 +++++++++--------- .../mindustry/desktop/DesktopPlatform.java | 7 + .../io/anuke/kryonet/DefaultThreadImpl.java | 40 ++++++ 13 files changed, 248 insertions(+), 120 deletions(-) create mode 100644 core/src/io/anuke/mindustry/core/ThreadHandler.java create mode 100644 kryonet/src/io/anuke/kryonet/DefaultThreadImpl.java diff --git a/android/src/io/anuke/mindustry/AndroidLauncher.java b/android/src/io/anuke/mindustry/AndroidLauncher.java index 77b79e5c31..0dbcb13cac 100644 --- a/android/src/io/anuke/mindustry/AndroidLauncher.java +++ b/android/src/io/anuke/mindustry/AndroidLauncher.java @@ -9,8 +9,10 @@ import android.os.Bundle; import android.telephony.TelephonyManager; import com.badlogic.gdx.backends.android.AndroidApplication; import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration; +import io.anuke.kryonet.DefaultThreadImpl; import io.anuke.kryonet.KryoClient; import io.anuke.kryonet.KryoServer; +import io.anuke.mindustry.core.ThreadHandler.ThreadProvider; import io.anuke.mindustry.io.Platform; import io.anuke.mindustry.net.Net; import io.anuke.ucore.scene.ui.TextField; @@ -85,6 +87,11 @@ public class AndroidLauncher extends AndroidApplication{ } } } + + @Override + public ThreadProvider getThreadProvider() { + return new DefaultThreadImpl(); + } }; if(doubleScaleTablets && isTablet(this.getContext())){ diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index ffb08eff5c..e41cf25858 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -217,6 +217,7 @@ setting.sensitivity.name=Controller Sensitivity setting.saveinterval.name=Autosave Interval setting.seconds={0} Seconds setting.fullscreen.name=Fullscreen +setting.multithread.name=Multithreading [scarlet](unstable!) setting.fps.name=Show FPS setting.vsync.name=VSync setting.lasers.name=Show Power Lasers diff --git a/core/src/io/anuke/mindustry/Mindustry.java b/core/src/io/anuke/mindustry/Mindustry.java index 7ea13b75b2..1bc490e7d4 100644 --- a/core/src/io/anuke/mindustry/Mindustry.java +++ b/core/src/io/anuke/mindustry/Mindustry.java @@ -1,21 +1,15 @@ package io.anuke.mindustry; -import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.utils.TimeUtils; import io.anuke.mindustry.core.*; import io.anuke.mindustry.io.BlockLoader; import io.anuke.mindustry.io.BundleLoader; import io.anuke.mindustry.io.Platform; -import io.anuke.ucore.core.Timers; import io.anuke.ucore.modules.ModuleCore; import io.anuke.ucore.util.Log; import static io.anuke.mindustry.Vars.*; public class Mindustry extends ModuleCore { - boolean multithread = true; - Thread thread; - float delta = 1f; @Override public void init(){ @@ -25,10 +19,7 @@ public class Mindustry extends ModuleCore { BundleLoader.load(); BlockLoader.load(); - logic = new Logic(); - - if(!multithread) module(logic); - + module(logic = new Logic()); module(world = new World()); module(control = new Control()); module(renderer = new Renderer()); @@ -36,49 +27,12 @@ public class Mindustry extends ModuleCore { module(netServer = new NetServer()); module(netClient = new NetClient()); module(netCommon = new NetCommon()); - - Timers.setDeltaProvider(() -> - Math.min(Thread.currentThread() == thread ? delta : Gdx.graphics.getDeltaTime()*60f, 20f) - ); - - if(multithread) { - - logic.init(); - - thread = new Thread(() -> { - try { - while (true) { - long time = TimeUtils.millis(); - logic.update(); - long elapsed = TimeUtils.timeSinceMillis(time); - long target = (long) (1000 / 60f); - - delta = Math.max(elapsed, target) / 1000f * 60f; - - if (elapsed < target) { - Thread.sleep(target - elapsed); - } - } - } catch (Exception ex) { - Gdx.app.postRunnable(() -> { - throw new RuntimeException(ex); - }); - } - }); - thread.setDaemon(true); - thread.setName("Update Thread"); - thread.start(); - } } + @Override public void render(){ super.render(); - - try { - //Thread.sleep(40); - }catch (Exception e){ - e.printStackTrace(); - } + threads.handleRender(); } } diff --git a/core/src/io/anuke/mindustry/Vars.java b/core/src/io/anuke/mindustry/Vars.java index 509b4550a1..d2032630c4 100644 --- a/core/src/io/anuke/mindustry/Vars.java +++ b/core/src/io/anuke/mindustry/Vars.java @@ -10,6 +10,7 @@ import io.anuke.mindustry.entities.Player; import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.entities.effect.Shield; import io.anuke.mindustry.entities.enemies.Enemy; +import io.anuke.mindustry.io.Platform; import io.anuke.mindustry.net.ClientDebug; import io.anuke.mindustry.net.ServerDebug; import io.anuke.ucore.UCore; @@ -112,6 +113,7 @@ public class Vars{ public static final int webPort = 6568; public static final GameState state = new GameState(); + public static final ThreadHandler threads = new ThreadHandler(Platform.instance.getThreadProvider()); public static final ServerDebug serverDebug = new ServerDebug(); public static final ClientDebug clientDebug = new ClientDebug(); diff --git a/core/src/io/anuke/mindustry/core/Control.java b/core/src/io/anuke/mindustry/core/Control.java index ba1980090c..89cb4cbe08 100644 --- a/core/src/io/anuke/mindustry/core/Control.java +++ b/core/src/io/anuke/mindustry/core/Control.java @@ -268,6 +268,13 @@ public class Control extends Module{ Entities.collisions().setCollider(tilesize, world::solid); Platform.instance.updateRPC(); + + //TODO remove + /* + Timers.runTask(2, () -> { + state.set(State.playing); + SaveIO.loadFromSlot(0); + });*/ } @Override diff --git a/core/src/io/anuke/mindustry/core/Logic.java b/core/src/io/anuke/mindustry/core/Logic.java index 993dd6a528..3491b81a3a 100644 --- a/core/src/io/anuke/mindustry/core/Logic.java +++ b/core/src/io/anuke/mindustry/core/Logic.java @@ -1,6 +1,5 @@ package io.anuke.mindustry.core; -import com.badlogic.gdx.Gdx; import com.badlogic.gdx.utils.Array; import io.anuke.mindustry.Vars; import io.anuke.mindustry.core.GameState.State; @@ -35,10 +34,6 @@ import static io.anuke.mindustry.Vars.*; public class Logic extends Module { private final Array spawns = WaveCreator.getSpawns(); - public Logic(){ - Timers.setDeltaProvider(() -> Math.min(Gdx.graphics.getDeltaTime()*60f, 60)); - } - @Override public void init(){ Entities.initPhysics(); @@ -111,6 +106,7 @@ public class Logic extends Module { @Override public void update(){ + if(!state.is(State.paused) || Net.active()){ Timers.update(); } diff --git a/core/src/io/anuke/mindustry/core/ThreadHandler.java b/core/src/io/anuke/mindustry/core/ThreadHandler.java new file mode 100644 index 0000000000..0b46272c55 --- /dev/null +++ b/core/src/io/anuke/mindustry/core/ThreadHandler.java @@ -0,0 +1,82 @@ +package io.anuke.mindustry.core; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.utils.TimeUtils; +import io.anuke.ucore.core.Timers; +import io.anuke.ucore.util.Log; + +import static io.anuke.mindustry.Vars.logic; + +public class ThreadHandler { + private final ThreadProvider impl; + private final Object lock = new Object(); + private float delta = 1f; + private boolean finished; + private boolean enabled; + + public ThreadHandler(ThreadProvider impl){ + this.impl = impl; + + Timers.setDeltaProvider(() -> impl.isOnThread() ? delta : Gdx.graphics.getDeltaTime()*60f); + } + + public void handleRender(){ + synchronized(lock) { + finished = true; + lock.notify(); + } + } + + public void setEnabled(boolean enabled){ + if(enabled){ + logic.doUpdate = false; + Timers.runTask(2f, () -> impl.start(this::runLogic)); + }else{ + impl.stop(); + Timers.runTask(2f, () -> logic.doUpdate = true); + } + this.enabled = enabled; + } + + public boolean isEnabled(){ + return enabled; + } + + private void runLogic(){ + try { + while (true) { + long time = TimeUtils.millis(); + logic.update(); + + long elapsed = TimeUtils.timeSinceMillis(time); + long target = (long) (1000 / 60f); + + delta = Math.max(elapsed, target) / 1000f * 60f; + + if (elapsed < target) { + impl.sleep(target - elapsed); + } + + synchronized(lock) { + while(!finished) { + lock.wait(); + } + finished = false; + } + } + } catch (InterruptedException ex) { + Log.info("Stopping logic thread."); + } catch (Exception ex) { + Gdx.app.postRunnable(() -> { + throw new RuntimeException(ex); + }); + } + } + + public interface ThreadProvider { + boolean isOnThread(); + void sleep(long ms) throws InterruptedException; + void start(Runnable run); + void stop(); + } +} diff --git a/core/src/io/anuke/mindustry/entities/TileEntity.java b/core/src/io/anuke/mindustry/entities/TileEntity.java index 816ddb428b..38631d9893 100644 --- a/core/src/io/anuke/mindustry/entities/TileEntity.java +++ b/core/src/io/anuke/mindustry/entities/TileEntity.java @@ -108,10 +108,10 @@ public class TileEntity extends Entity{ public void update(){ if(health != 0 && health < tile.block().health && !(tile.block() instanceof Wall) && Mathf.chance(0.009f*Timers.delta()*(1f-health/tile.block().health))){ - + Effects.effect(Fx.smoke, x+Mathf.range(4), y+Mathf.range(4)); } - + if(health <= 0){ onDeath(); } diff --git a/core/src/io/anuke/mindustry/io/Platform.java b/core/src/io/anuke/mindustry/io/Platform.java index ca2ae7c48e..9c0aab5fdd 100644 --- a/core/src/io/anuke/mindustry/io/Platform.java +++ b/core/src/io/anuke/mindustry/io/Platform.java @@ -1,5 +1,6 @@ package io.anuke.mindustry.io; +import io.anuke.mindustry.core.ThreadHandler.ThreadProvider; import io.anuke.ucore.scene.ui.TextField; import java.util.Date; @@ -27,4 +28,27 @@ public abstract class Platform { return true; } public boolean isDebug(){return false;} + public ThreadProvider getThreadProvider(){ + return new ThreadProvider() { + @Override + public boolean isOnThread() { + return true; + } + + @Override + public void sleep(long ms) { + + } + + @Override + public void start(Runnable run) { + + } + + @Override + public void stop() { + + } + }; + } } diff --git a/core/src/io/anuke/mindustry/ui/dialogs/SettingsMenuDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/SettingsMenuDialog.java index 780701f399..ca0c126cf6 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/SettingsMenuDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/SettingsMenuDialog.java @@ -113,6 +113,14 @@ public class SettingsMenuDialog extends SettingsDialog{ game.sliderPref("sensitivity", 100, 10, 300, i -> i + "%"); game.sliderPref("saveinterval", 90, 10, 5*120, i -> Bundles.format("setting.seconds", i)); + if(!gwt){ + graphics.checkPref("multithread", false, threads::setEnabled); + + if(Settings.getBool("multithread")){ + threads.setEnabled(true); + } + } + if(!android && !gwt) { graphics.checkPref("vsync", true, b -> Gdx.graphics.setVSync(b)); graphics.checkPref("fullscreen", false, b -> { @@ -128,6 +136,7 @@ public class SettingsMenuDialog extends SettingsDialog{ Gdx.graphics.setFullscreenMode(Gdx.graphics.getDisplayMode()); } } + graphics.checkPref("fps", false); graphics.checkPref("lasers", true); graphics.checkPref("indicators", true); diff --git a/core/src/io/anuke/mindustry/world/blocks/types/distribution/Conveyor.java b/core/src/io/anuke/mindustry/world/blocks/types/distribution/Conveyor.java index cc46d45bad..dac7eeff7f 100644 --- a/core/src/io/anuke/mindustry/world/blocks/types/distribution/Conveyor.java +++ b/core/src/io/anuke/mindustry/world/blocks/types/distribution/Conveyor.java @@ -28,36 +28,37 @@ public class Conveyor extends Block{ private static final float itemSpace = 0.135f; private static final float offsetScl = 128f*3f; private static final float itemSize = 4f; + private static final float minmove = 1f / (Short.MAX_VALUE - 2); private final Translator tr1 = new Translator(); private final Translator tr2 = new Translator(); - + public float speed = 0.02f; - + protected Conveyor(String name) { super(name); rotate = true; update = true; layer = Layer.overlay; } - + @Override public void getStats(Array list){ super.getStats(list); list.add("[iteminfo]Item Speed/second: " + Strings.toFixed(speed * 60, 1)); } - + @Override public boolean canReplace(Block other){ return other instanceof Conveyor || other instanceof Router || other instanceof Junction; } - + @Override public void draw(Tile tile){ byte rotation = tile.getRotation(); - - Draw.rect(name() + - (Timers.time() % ((20 / 100f) / speed) < (10 / 100f) / speed && acceptItem(Item.stone, tile, null) ? "" : "move"), + + Draw.rect(name() + + (Timers.time() % ((20 / 100f) / speed) < (10 / 100f) / speed && acceptItem(Item.stone, tile, null) ? "" : "move"), tile.worldx(), tile.worldy(), rotation * 90); } @@ -65,13 +66,13 @@ public class Conveyor extends Block{ public boolean isLayer(Tile tile){ return tile.entity().convey.size > 0; } - + @Override - public void drawLayer(Tile tile){ + public synchronized void drawLayer(Tile tile){ ConveyorEntity entity = tile.entity(); - + byte rotation = tile.getRotation(); - + for(int i = 0; i < entity.convey.size; i ++){ ItemPos pos = drawpos.set(entity.convey.get(i)); @@ -79,19 +80,19 @@ public class Conveyor extends Block{ tr1.trns(rotation * 90, tilesize, 0); tr2.trns(rotation * 90, -tilesize / 2, pos.x*tilesize/2); - + Draw.rect(pos.item.region, tile.x * tilesize + tr1.x * pos.y + tr2.x, tile.y * tilesize + tr1.y * pos.y + tr2.y, itemSize, itemSize); } } - + @Override - public void update(Tile tile){ - + public synchronized void update(Tile tile){ + ConveyorEntity entity = tile.entity(); entity.minitem = 1f; - + removals.clear(); float shift = entity.elapsed * speed; @@ -106,37 +107,35 @@ public class Conveyor extends Block{ removals.add(value); continue; } - - boolean canmove = i == entity.convey.size - 1 || - !(pos2.set(entity.convey.get(i + 1)).y - pos.y < itemSpace * Math.max(Timers.delta(), 1f)); - float minmove = 1f / (Short.MAX_VALUE - 2); + float nextpos = (i == entity.convey.size - 1 ? 100f : pos2.set(entity.convey.get(i + 1)).y) - itemSpace; + float maxmove = Math.min(nextpos - pos.y, speed * Timers.delta()); - if(canmove){ - pos.y += Math.max(speed * Timers.delta(), minmove); //TODO fix precision issues when at high FPS? + if(maxmove > minmove){ + pos.y += maxmove; pos.x = Mathf.lerpDelta(pos.x, 0, 0.06f); }else{ pos.x = Mathf.lerpDelta(pos.x, pos.seed/offsetScl, 0.1f); } - + pos.y = Mathf.clamp(pos.y); - + if(pos.y >= 0.9999f && offloadDir(tile, pos.item)){ removals.add(value); }else{ value = pos.pack(); - + if(pos.y < entity.minitem) entity.minitem = pos.y; entity.convey.set(i, value); } - + } entity.elapsed = 0f; entity.convey.removeAll(removals); } - + @Override public TileEntity getEntity(){ return new ConveyorEntity(); @@ -146,25 +145,25 @@ public class Conveyor extends Block{ public boolean acceptItem(Item item, Tile tile, Tile source){ int direction = source == null ? 0 : Math.abs(source.relativeTo(tile.x, tile.y) - tile.getRotation()); float minitem = tile.entity().minitem; - return (((direction == 0) && minitem > 0.05f) || + return (((direction == 0) && minitem > 0.05f) || ((direction %2 == 1) && minitem > 0.52f)) && (source == null || !(source.block().rotate && (source.getRotation() + 2) % 4 == tile.getRotation())); } @Override public void handleItem(Item item, Tile tile, Tile source){ byte rotation = tile.getRotation(); - + int ch = Math.abs(source.relativeTo(tile.x, tile.y) - rotation); int ang = ((source.relativeTo(tile.x, tile.y) - rotation)); - + float pos = ch == 0 ? 0 : ch % 2 == 1 ? 0.5f : 1f; float y = (ang == -1 || ang == 3) ? 1 : (ang == 1 || ang == -3) ? -1 : 0; - + ConveyorEntity entity = tile.entity(); long result = ItemPos.packItem(item, y*0.9f, pos, (byte)Mathf.random(255)); boolean inserted = false; - + for(int i = 0; i < entity.convey.size; i ++){ if(compareItems(result, entity.convey.get(i)) < 0){ entity.convey.insert(i, result); @@ -172,13 +171,13 @@ public class Conveyor extends Block{ break; } } - + //this item must be greater than anything there... if(!inserted){ entity.convey.add(result); } } - + /** * Conveyor data format: * [0] item ordinal @@ -191,26 +190,26 @@ public class Conveyor extends Block{ LongArray convey = new LongArray(); float minitem = 1, elapsed; - + @Override public void write(DataOutputStream stream) throws IOException{ stream.writeInt(convey.size); - + for(int i = 0; i < convey.size; i ++){ stream.writeInt(ItemPos.toInt(convey.get(i))); } } - + @Override public void read(DataInputStream stream) throws IOException{ convey.clear(); int amount = stream.readInt(); convey.ensureCapacity(amount); - + for(int i = 0; i < amount; i ++){ convey.add(ItemPos.toLong(stream.readInt())); } - + sort(convey.items, convey.size); } @@ -220,37 +219,37 @@ public class Conveyor extends Block{ this.elapsed = elapsed; } } - + private static void sort(long[] elements, int length){ List wrapper = new AbstractList() { - @Override - public Long get(int index) { - return elements[index]; - } + @Override + public Long get(int index) { + return elements[index]; + } - @Override - public int size() { - return length; - } + @Override + public int size() { + return length; + } - @Override - public Long set(int index, Long element) { - long v = elements[index]; - elements[index] = element; - return v; - } - }; - - Collections.sort(wrapper, Conveyor::compareItems); + @Override + public Long set(int index, Long element) { + long v = elements[index]; + elements[index] = element; + return v; + } + }; + + Collections.sort(wrapper, Conveyor::compareItems); } - + private static int compareItems(Long a, Long b){ pos1.set(a); pos2.set(b); return Float.compare(pos1.y, pos2.y); } - + //Container class. Do not instantiate. static class ItemPos{ private static short[] writeShort = new short[4]; @@ -259,7 +258,7 @@ public class Conveyor extends Block{ Item item; float x, y; byte seed; - + private ItemPos(){} ItemPos set(long lvalue){ @@ -275,11 +274,11 @@ public class Conveyor extends Block{ seed = (byte)values[3]; return this; } - + long pack(){ return packItem(item, x, y, seed); } - + static long packItem(Item item, float x, float y, byte seed){ short[] shorts = Bits.getShorts(); shorts[0] = (short)item.id; @@ -322,4 +321,4 @@ public class Conveyor extends Block{ return Bits.packLong(shorts); } } -} +} \ No newline at end of file diff --git a/desktop/src/io/anuke/mindustry/desktop/DesktopPlatform.java b/desktop/src/io/anuke/mindustry/desktop/DesktopPlatform.java index 7962856ec3..f925c59602 100644 --- a/desktop/src/io/anuke/mindustry/desktop/DesktopPlatform.java +++ b/desktop/src/io/anuke/mindustry/desktop/DesktopPlatform.java @@ -3,7 +3,9 @@ package io.anuke.mindustry.desktop; import club.minnced.discord.rpc.DiscordEventHandlers; import club.minnced.discord.rpc.DiscordRPC; import club.minnced.discord.rpc.DiscordRichPresence; +import io.anuke.kryonet.DefaultThreadImpl; import io.anuke.mindustry.core.GameState.State; +import io.anuke.mindustry.core.ThreadHandler.ThreadProvider; import io.anuke.mindustry.io.Platform; import io.anuke.mindustry.net.Net; import io.anuke.ucore.util.Strings; @@ -85,4 +87,9 @@ public class DesktopPlatform extends Platform { public boolean isDebug() { return args.length > 0 && args[0].equalsIgnoreCase("-debug"); } + + @Override + public ThreadProvider getThreadProvider() { + return new DefaultThreadImpl(); + } } diff --git a/kryonet/src/io/anuke/kryonet/DefaultThreadImpl.java b/kryonet/src/io/anuke/kryonet/DefaultThreadImpl.java new file mode 100644 index 0000000000..64e8b62240 --- /dev/null +++ b/kryonet/src/io/anuke/kryonet/DefaultThreadImpl.java @@ -0,0 +1,40 @@ +package io.anuke.kryonet; + +import io.anuke.mindustry.core.ThreadHandler.ThreadProvider; +import io.anuke.ucore.util.Log; + +public class DefaultThreadImpl implements ThreadProvider { + private Thread thread; + + @Override + public boolean isOnThread() { + return Thread.currentThread() == thread; + } + + @Override + public void sleep(long ms) throws InterruptedException{ + Thread.sleep(ms); + } + + @Override + public void start(Runnable run) { + if(thread != null){ + thread.interrupt(); + thread = null; + } + + thread = new Thread(run); + thread.setDaemon(true); + thread.setName("Update Thread"); + thread.start(); + Log.info("Starting logic thread."); + } + + @Override + public void stop() { + if(thread != null){ + thread.interrupt(); + thread = null; + } + } +}