1
0
mirror of https://github.com/Anuken/Mindustry.git synced 2024-09-21 21:38:29 +03:00

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 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())){

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

@ -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<String> 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.<ConveyorEntity>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.<ConveyorEntity>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<Long> wrapper = new AbstractList<Long>() {
@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);
}
}
}
}

View File

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

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