Implemented toggling of multithreading

This commit is contained in:
Anuken
2018-02-07 14:47:39 -05:00
parent 1a6f773ddb
commit 460558bc87
13 changed files with 248 additions and 120 deletions

View File

@@ -9,8 +9,10 @@ import android.os.Bundle;
import android.telephony.TelephonyManager; import android.telephony.TelephonyManager;
import com.badlogic.gdx.backends.android.AndroidApplication; import com.badlogic.gdx.backends.android.AndroidApplication;
import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration; import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration;
import io.anuke.kryonet.DefaultThreadImpl;
import io.anuke.kryonet.KryoClient; import io.anuke.kryonet.KryoClient;
import io.anuke.kryonet.KryoServer; import io.anuke.kryonet.KryoServer;
import io.anuke.mindustry.core.ThreadHandler.ThreadProvider;
import io.anuke.mindustry.io.Platform; import io.anuke.mindustry.io.Platform;
import io.anuke.mindustry.net.Net; import io.anuke.mindustry.net.Net;
import io.anuke.ucore.scene.ui.TextField; 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())){ if(doubleScaleTablets && isTablet(this.getContext())){

View File

@@ -217,6 +217,7 @@ setting.sensitivity.name=Controller Sensitivity
setting.saveinterval.name=Autosave Interval setting.saveinterval.name=Autosave Interval
setting.seconds={0} Seconds setting.seconds={0} Seconds
setting.fullscreen.name=Fullscreen setting.fullscreen.name=Fullscreen
setting.multithread.name=Multithreading [scarlet](unstable!)
setting.fps.name=Show FPS setting.fps.name=Show FPS
setting.vsync.name=VSync setting.vsync.name=VSync
setting.lasers.name=Show Power Lasers setting.lasers.name=Show Power Lasers

View File

@@ -1,21 +1,15 @@
package io.anuke.mindustry; package io.anuke.mindustry;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.utils.TimeUtils;
import io.anuke.mindustry.core.*; import io.anuke.mindustry.core.*;
import io.anuke.mindustry.io.BlockLoader; import io.anuke.mindustry.io.BlockLoader;
import io.anuke.mindustry.io.BundleLoader; import io.anuke.mindustry.io.BundleLoader;
import io.anuke.mindustry.io.Platform; import io.anuke.mindustry.io.Platform;
import io.anuke.ucore.core.Timers;
import io.anuke.ucore.modules.ModuleCore; import io.anuke.ucore.modules.ModuleCore;
import io.anuke.ucore.util.Log; import io.anuke.ucore.util.Log;
import static io.anuke.mindustry.Vars.*; import static io.anuke.mindustry.Vars.*;
public class Mindustry extends ModuleCore { public class Mindustry extends ModuleCore {
boolean multithread = true;
Thread thread;
float delta = 1f;
@Override @Override
public void init(){ public void init(){
@@ -25,10 +19,7 @@ public class Mindustry extends ModuleCore {
BundleLoader.load(); BundleLoader.load();
BlockLoader.load(); BlockLoader.load();
logic = new Logic(); module(logic = new Logic());
if(!multithread) module(logic);
module(world = new World()); module(world = new World());
module(control = new Control()); module(control = new Control());
module(renderer = new Renderer()); module(renderer = new Renderer());
@@ -36,49 +27,12 @@ public class Mindustry extends ModuleCore {
module(netServer = new NetServer()); module(netServer = new NetServer());
module(netClient = new NetClient()); module(netClient = new NetClient());
module(netCommon = new NetCommon()); 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(){ public void render(){
super.render(); super.render();
threads.handleRender();
try {
//Thread.sleep(40);
}catch (Exception e){
e.printStackTrace();
}
} }
} }

View File

@@ -10,6 +10,7 @@ import io.anuke.mindustry.entities.Player;
import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.entities.TileEntity;
import io.anuke.mindustry.entities.effect.Shield; import io.anuke.mindustry.entities.effect.Shield;
import io.anuke.mindustry.entities.enemies.Enemy; import io.anuke.mindustry.entities.enemies.Enemy;
import io.anuke.mindustry.io.Platform;
import io.anuke.mindustry.net.ClientDebug; import io.anuke.mindustry.net.ClientDebug;
import io.anuke.mindustry.net.ServerDebug; import io.anuke.mindustry.net.ServerDebug;
import io.anuke.ucore.UCore; import io.anuke.ucore.UCore;
@@ -112,6 +113,7 @@ public class Vars{
public static final int webPort = 6568; public static final int webPort = 6568;
public static final GameState state = new GameState(); 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 ServerDebug serverDebug = new ServerDebug();
public static final ClientDebug clientDebug = new ClientDebug(); public static final ClientDebug clientDebug = new ClientDebug();

View File

@@ -268,6 +268,13 @@ public class Control extends Module{
Entities.collisions().setCollider(tilesize, world::solid); Entities.collisions().setCollider(tilesize, world::solid);
Platform.instance.updateRPC(); Platform.instance.updateRPC();
//TODO remove
/*
Timers.runTask(2, () -> {
state.set(State.playing);
SaveIO.loadFromSlot(0);
});*/
} }
@Override @Override

View File

@@ -1,6 +1,5 @@
package io.anuke.mindustry.core; package io.anuke.mindustry.core;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Array;
import io.anuke.mindustry.Vars; import io.anuke.mindustry.Vars;
import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.core.GameState.State;
@@ -35,10 +34,6 @@ import static io.anuke.mindustry.Vars.*;
public class Logic extends Module { public class Logic extends Module {
private final Array<EnemySpawn> spawns = WaveCreator.getSpawns(); private final Array<EnemySpawn> spawns = WaveCreator.getSpawns();
public Logic(){
Timers.setDeltaProvider(() -> Math.min(Gdx.graphics.getDeltaTime()*60f, 60));
}
@Override @Override
public void init(){ public void init(){
Entities.initPhysics(); Entities.initPhysics();
@@ -111,6 +106,7 @@ public class Logic extends Module {
@Override @Override
public void update(){ public void update(){
if(!state.is(State.paused) || Net.active()){ if(!state.is(State.paused) || Net.active()){
Timers.update(); Timers.update();
} }

View File

@@ -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();
}
}

View File

@@ -1,5 +1,6 @@
package io.anuke.mindustry.io; package io.anuke.mindustry.io;
import io.anuke.mindustry.core.ThreadHandler.ThreadProvider;
import io.anuke.ucore.scene.ui.TextField; import io.anuke.ucore.scene.ui.TextField;
import java.util.Date; import java.util.Date;
@@ -27,4 +28,27 @@ public abstract class Platform {
return true; return true;
} }
public boolean isDebug(){return false;} 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() {
}
};
}
} }

View File

@@ -113,6 +113,14 @@ public class SettingsMenuDialog extends SettingsDialog{
game.sliderPref("sensitivity", 100, 10, 300, i -> i + "%"); game.sliderPref("sensitivity", 100, 10, 300, i -> i + "%");
game.sliderPref("saveinterval", 90, 10, 5*120, i -> Bundles.format("setting.seconds", 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) { if(!android && !gwt) {
graphics.checkPref("vsync", true, b -> Gdx.graphics.setVSync(b)); graphics.checkPref("vsync", true, b -> Gdx.graphics.setVSync(b));
graphics.checkPref("fullscreen", false, b -> { graphics.checkPref("fullscreen", false, b -> {
@@ -128,6 +136,7 @@ public class SettingsMenuDialog extends SettingsDialog{
Gdx.graphics.setFullscreenMode(Gdx.graphics.getDisplayMode()); Gdx.graphics.setFullscreenMode(Gdx.graphics.getDisplayMode());
} }
} }
graphics.checkPref("fps", false); graphics.checkPref("fps", false);
graphics.checkPref("lasers", true); graphics.checkPref("lasers", true);
graphics.checkPref("indicators", true); graphics.checkPref("indicators", true);

View File

@@ -28,6 +28,7 @@ public class Conveyor extends Block{
private static final float itemSpace = 0.135f; private static final float itemSpace = 0.135f;
private static final float offsetScl = 128f*3f; private static final float offsetScl = 128f*3f;
private static final float itemSize = 4f; 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 tr1 = new Translator();
private final Translator tr2 = new Translator(); private final Translator tr2 = new Translator();
@@ -57,7 +58,7 @@ public class Conveyor extends Block{
byte rotation = tile.getRotation(); byte rotation = tile.getRotation();
Draw.rect(name() + Draw.rect(name() +
(Timers.time() % ((20 / 100f) / speed) < (10 / 100f) / speed && acceptItem(Item.stone, tile, null) ? "" : "move"), (Timers.time() % ((20 / 100f) / speed) < (10 / 100f) / speed && acceptItem(Item.stone, tile, null) ? "" : "move"),
tile.worldx(), tile.worldy(), rotation * 90); tile.worldx(), tile.worldy(), rotation * 90);
} }
@@ -67,7 +68,7 @@ public class Conveyor extends Block{
} }
@Override @Override
public void drawLayer(Tile tile){ public synchronized void drawLayer(Tile tile){
ConveyorEntity entity = tile.entity(); ConveyorEntity entity = tile.entity();
byte rotation = tile.getRotation(); byte rotation = tile.getRotation();
@@ -87,7 +88,7 @@ public class Conveyor extends Block{
} }
@Override @Override
public void update(Tile tile){ public synchronized void update(Tile tile){
ConveyorEntity entity = tile.entity(); ConveyorEntity entity = tile.entity();
entity.minitem = 1f; entity.minitem = 1f;
@@ -107,13 +108,11 @@ public class Conveyor extends Block{
continue; continue;
} }
boolean canmove = i == entity.convey.size - 1 || float nextpos = (i == entity.convey.size - 1 ? 100f : pos2.set(entity.convey.get(i + 1)).y) - itemSpace;
!(pos2.set(entity.convey.get(i + 1)).y - pos.y < itemSpace * Math.max(Timers.delta(), 1f)); float maxmove = Math.min(nextpos - pos.y, speed * Timers.delta());
float minmove = 1f / (Short.MAX_VALUE - 2); if(maxmove > minmove){
pos.y += maxmove;
if(canmove){
pos.y += Math.max(speed * Timers.delta(), minmove); //TODO fix precision issues when at high FPS?
pos.x = Mathf.lerpDelta(pos.x, 0, 0.06f); pos.x = Mathf.lerpDelta(pos.x, 0, 0.06f);
}else{ }else{
pos.x = Mathf.lerpDelta(pos.x, pos.seed/offsetScl, 0.1f); pos.x = Mathf.lerpDelta(pos.x, pos.seed/offsetScl, 0.1f);
@@ -224,25 +223,25 @@ public class Conveyor extends Block{
private static void sort(long[] elements, int length){ private static void sort(long[] elements, int length){
List<Long> wrapper = new AbstractList<Long>() { List<Long> wrapper = new AbstractList<Long>() {
@Override @Override
public Long get(int index) { public Long get(int index) {
return elements[index]; return elements[index];
} }
@Override @Override
public int size() { public int size() {
return length; return length;
} }
@Override @Override
public Long set(int index, Long element) { public Long set(int index, Long element) {
long v = elements[index]; long v = elements[index];
elements[index] = element; elements[index] = element;
return v; return v;
} }
}; };
Collections.sort(wrapper, Conveyor::compareItems); Collections.sort(wrapper, Conveyor::compareItems);
} }
private static int compareItems(Long a, Long b){ private static int compareItems(Long a, Long b){

View File

@@ -3,7 +3,9 @@ package io.anuke.mindustry.desktop;
import club.minnced.discord.rpc.DiscordEventHandlers; import club.minnced.discord.rpc.DiscordEventHandlers;
import club.minnced.discord.rpc.DiscordRPC; import club.minnced.discord.rpc.DiscordRPC;
import club.minnced.discord.rpc.DiscordRichPresence; import club.minnced.discord.rpc.DiscordRichPresence;
import io.anuke.kryonet.DefaultThreadImpl;
import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.core.GameState.State;
import io.anuke.mindustry.core.ThreadHandler.ThreadProvider;
import io.anuke.mindustry.io.Platform; import io.anuke.mindustry.io.Platform;
import io.anuke.mindustry.net.Net; import io.anuke.mindustry.net.Net;
import io.anuke.ucore.util.Strings; import io.anuke.ucore.util.Strings;
@@ -85,4 +87,9 @@ public class DesktopPlatform extends Platform {
public boolean isDebug() { public boolean isDebug() {
return args.length > 0 && args[0].equalsIgnoreCase("-debug"); return args.length > 0 && args[0].equalsIgnoreCase("-debug");
} }
@Override
public ThreadProvider getThreadProvider() {
return new DefaultThreadImpl();
}
} }

View File

@@ -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;
}
}
}