Skip to content

Commit

Permalink
- Fix schematic browser lag
Browse files Browse the repository at this point in the history
- Fix schematic browser continuing to consume memory when closed
  • Loading branch information
buthed010203 committed Jan 3, 2024
1 parent 38f19b4 commit 8f52ef5
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 55 deletions.
6 changes: 3 additions & 3 deletions core/assets/bundles/bundle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -1443,11 +1443,11 @@ setting.returnonmove.name = Reset Camera On Move
setting.returnonmove.description = Stops freecam on player movement as in vanilla
setting.nostrafepenalty.name = No strafing speed penalty
setting.nostrafepenalty.description = Ignores speed reduction when not moving in the direction your unit is facing (doesn't work on servers)
setting.zerodrift.name = Zero drift
setting.zerodrift.description = Eliminates drifting after releasing movement keys. Useful for precise movement.
setting.decreasedrift.name = Decreased Drift
setting.decreasedrift.description = Decreases drift on higher speeds.
setting.fastrespawn.name = Fast respawn
setting.zerodrift.name = Zero Drift
setting.zerodrift.description = Eliminates drifting after releasing movement keys. Useful for precise movement.
setting.fastrespawn.name = Fast Respawn
setting.fastrespawn.description = Ignores respawn delay and immediately respawns you.

setting.graphics.category = Graphics & Appearance
Expand Down
2 changes: 1 addition & 1 deletion core/src/mindustry/client/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ object Main : ApplicationListener {
}
}

fun send(transmission: Transmission, onFinish: (() -> Unit)? = null) {
fun send(transmission: Transmission, onFinish: Runnable? = null) {
communicationClient.send(transmission, onFinish)
}

Expand Down
6 changes: 3 additions & 3 deletions core/src/mindustry/client/communication/Packets.kt
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ object Packets {
val toSend = outgoing.peek() ?: return // Return if there's nothing to send

// Gets the next packet in this transmission, if there are no more packets move to the next transmission
val packet = toSend.packets.poll() ?: run { outgoing.remove(toSend); toSend.onFinish?.invoke(); return }
val packet = toSend.packets.poll() ?: run { outgoing.remove(toSend); toSend.onFinish?.run(); return }

lastSent.reset(0, 0f) // Sending a packet, reset the timer fully
try { communicationSystem.send(packet.bytes()) } catch (e: Exception) { outgoing.remove(toSend); toSend.onError?.invoke() }
Expand Down Expand Up @@ -213,15 +213,15 @@ object Packets {
} catch (e: Exception) { Log.err(e) }
}

private data class OutgoingTransmission(val packets: Queue<Packet>, val onFinish: (() -> Unit)?, val onError: (() -> Unit)?)
private data class OutgoingTransmission(val packets: Queue<Packet>, val onFinish: Runnable?, val onError: (() -> Unit)?)

/**
* Splits the transmission into packets and queues them for sending.
* @param transmission The transmission to be sent.
* @param onFinish A lambda that will be run once it is sent, null by default.
* @param onError A lambda that will be run when no suitable message block is found.
*/
fun send(transmission: Transmission, onFinish: (() -> Unit)? = null, onError: (() -> Unit)? = null) {
fun send(transmission: Transmission, onFinish: Runnable? = null, onError: (() -> Unit)? = null) {
val type = registeredTransmissionTypes.indexOfFirst { it.type == transmission::class }

if (transmission.secureOnly && !communicationSystem.secure) throw IllegalArgumentException("Communications system must be secure to send secure-only transmissions!")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
package mindustry.ui.dialogs;
package mindustry.client.ui;

import arc.*;
import arc.files.*;
import arc.graphics.*;
import arc.graphics.gl.*;
import arc.math.geom.*;
import arc.scene.actions.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import kotlin.Unit;
import mindustry.client.*;
import mindustry.client.communication.*;
import mindustry.client.navigation.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.ui.dialogs.*;

import java.util.function.*;
import java.util.regex.*;
Expand All @@ -34,16 +38,16 @@ public class SchematicBrowserDialog extends BaseDialog {
private Runnable rebuildPane = () -> {}, rebuildTags = () -> {};
private final Pattern ignoreSymbols = Pattern.compile("[`~!@#$%^&*()\\-_=+{}|;:'\",<.>/?]");
private final Seq<String> tags = new Seq<>(), selectedTags = new Seq<>();
private final ItemSeq reusableItemSeq = new ItemSeq();

public SchematicBrowserDialog(){
super("@schematic.browser");
Core.assets.load("sprites/schematic-background.png", Texture.class).loaded = t -> t.setWrap(Texture.TextureWrap.repeat);

shouldPause = true;
addCloseButton();
buttons.button("@schematic", Icon.copy, this::hideBrowser);
buttons.button("@schematic.browser.repo", Icon.host, this.repositoriesDialog::show);
buttons.button("@schematic.browser.fetch", Icon.refresh, () -> fetch(loadedRepositories.keys().toSeq()));
buttons.button("@schematics", Icon.copy, this::hideBrowser);
buttons.button("@schematic.browser.repo", Icon.host, repositoriesDialog::show);
buttons.button("@schematic.browser.fetch", Icon.refresh, () -> fetch(repositoryLinks));
makeButtonOverlay();

getSettings();
Expand All @@ -52,9 +56,36 @@ public SchematicBrowserDialog(){

shown(this::setup);
onResize(this::setup);

setHideAction(() -> Actions.run(() -> { // Nuke previews to save ram FINISHME: Nuke the schematics as well and reload them on dialog open. Ideally, we should do that across all threads similar to how we load saves
var previews = Reflect.<OrderedMap<Schematic, FrameBuffer>>get(schematics, "previews");
var removed = new Queue<FrameBuffer>();
for (var schems : loadedRepositories.values()) {
for (var schem : schems) {
var rem = previews.remove(schem);
if (rem != null) removed.add(rem);
}
}
Core.app.post(() -> disposeBuffers(removed)); // Start removing next frame as the process above may already take a few msec on slow cpus or in large repositories
}));
}

/** Disposes a list of FrameBuffers over the course of multiple frames to not cause lag. */
void disposeBuffers(Queue<FrameBuffer> todo) {
var start = Time.nanos();
while (!todo.isEmpty()) {
if (Time.millisSinceNanos(start) >= 5) {
Log.debug("Couldn't finish disposing buffers in time, resuming next frame. @ remain", todo.size);
Core.app.post(() -> disposeBuffers(todo));
return;
}
todo.removeFirst().dispose();
}
Log.debug("Finished disposing of FrameBuffers");
}

void setup(){
Time.mark();
search = "";

cont.top();
Expand Down Expand Up @@ -100,18 +131,30 @@ void setup(){
}).height(tagh).fillX();
cont.row();

cont.pane(t -> {
t.top();
rebuildPane = () -> {
t.clear();
firstSchematic = null;
for (String repo : loadedRepositories.keys()) {
if (hiddenRepositories.contains(repo)) continue;
setupRepoUi(t, ignoreSymbols.matcher(search.toLowerCase()).replaceAll(""), repo);
}
};
rebuildPane.run();
}).grow().scrollX(false);
Table[] t = {null}; // Peak java
t[0] = new Table() {
@Override
public void setCullingArea(Rect cullingArea) {
super.setCullingArea(cullingArea);
t[0].getChildren().<Table>each(c -> c instanceof Table, c -> {
var area = t[0].getCullingArea();
c.getCullingArea().setSize(area.width, area.height) // Set the size (NOT scaled to child coordinates which it should be if either scale isn't 1)
.setPosition(c.parentToLocalCoordinates(area.getPosition(Tmp.v1))); // Set the position (scaled correctly)
});
}
};
t[0].top();
rebuildPane = () -> {
t[0].clear();
firstSchematic = null;
for (String repo : loadedRepositories.keys()) {
if (hiddenRepositories.contains(repo)) continue;
setupRepoUi(t[0], ignoreSymbols.matcher(search.toLowerCase()).replaceAll(""), repo);
}
};
rebuildPane.run();
cont.pane(t[0]).grow().scrollX(false);
Log.info("Rebuilt Schematic Browser in @ms", Time.elapsed());
}

void setupRepoUi(Table table, String searchString, String repo){
Expand All @@ -122,10 +165,12 @@ void setupRepoUi(Table table, String searchString, String repo){
table.image().growX().padTop(10).height(3).color(Pal.accent).center();
table.row();
table.table(t -> {
int i = 0;
t.setCullingArea(new Rect()); // Make sure this isn't null for later

int[] i = {0};
final int max = Core.settings.getInt("maxschematicslisted");
for(Schematic s : loadedRepositories.get(repo)){
if(max != 0 && i > max) break; // Avoid meltdown on large repositories
if(max != 0 && i[0] > max) break; // Avoid meltdown on large repositories

if(selectedTags.any() && !s.labels.containsAll(selectedTags)) continue; // Tags
if(!search.isEmpty() && !(ignoreSymbols.matcher(s.name().toLowerCase()).replaceAll("").contains(searchString)
Expand All @@ -148,15 +193,24 @@ void setupRepoUi(Table table, String searchString, String repo){
buttons.button(Icon.download, style, () -> {
ui.showInfoFade("@schematic.saved");
schematics.add(s);
ui.schematics.checkTags(s);
Reflect.invoke(ui.schematics, "checkTags", new Object[]{s}, Schematic.class); // Vars.ui.schematics.checkTags(s)
}).tooltip("@schematic.browser.download");
}).growX().height(50f);
b.row();
b.stack(new SchematicsDialog.SchematicImage(s).setScaling(Scaling.fit), new Table(n -> {
n.top();
n.table(Styles.black3, c -> {
Label label = c.add(s.name()).style(Styles.outlineLabel).top().growX().maxWidth(200f - 8f)
.update(l -> l.setText((!player.team().rules().infiniteResources && !state.rules.infiniteResources && player.core() != null && !player.core().items.has(s.requirements()) ? "[#dd5656]" : "") + s.name())).get();
Label label = c.add("").style(Styles.outlineLabel).top().growX().maxWidth(200f - 8f)
.update(l -> {
var txt = l.getText(); // Update the stringBuilder directly
if (txt.length() == 0 || (Core.graphics.getFrameId() + i[0]) % 60 == 0) { // update() is run every frame even when the element is culled out, the solution is to only update a portion every frame FINISHME: Do we want to hack this and update the text in the draw() method which is only called when the element isn't culled?
txt.setLength(0);
if (!player.team().rules().infiniteResources && !state.rules.infiniteResources && player.core() != null && !player.core().items.has(s.requirements(reusableItemSeq))) txt.append("[#dd5656]");
txt.append(s.name());
reusableItemSeq.clear();
}
}).get();
label.runUpdate(); // Update the text instantly
label.setEllipsis(true);
label.setAlignment(Align.center);
}).growX().margin(1).pad(4).maxWidth(Scl.scl(200f - 8f)).padBottom(0);
Expand All @@ -177,12 +231,12 @@ void setupRepoUi(Table table, String searchString, String repo){

sel[0].getStyle().up = Tex.pane;

if(++i % cols == 0){
if(++i[0] % cols == 0){
t.row();
}
}

if(i==0){
if(i[0]==0){
if(!searchString.isEmpty() || selectedTags.any()){
t.add("@none.found");
}else{
Expand Down Expand Up @@ -224,10 +278,9 @@ public void showExport(Schematic s){
t.button("@schematic.chatshare", Icon.bookOpen, style, () -> {
if (!state.isPlaying()) return;
dialog.hide();
clientThread.post(() -> Main.INSTANCE.send(new SchematicTransmission(s), () -> {
Core.app.post(() -> ui.showInfoToast(Core.bundle.get("client.finisheduploading"), 2f));
return Unit.INSTANCE;
}));
clientThread.post(() -> Main.INSTANCE.send(new SchematicTransmission(s), () -> Core.app.post(() ->
ui.showInfoToast(Core.bundle.get("client.finisheduploading"), 2f)
)));
}).marginLeft(12f).get().setDisabled(() -> !state.isPlaying());
});
});
Expand Down Expand Up @@ -394,7 +447,7 @@ void showAllTags(){

void hideBrowser(){
ui.schematics.show();
this.hide();
hide();
}

void getSettings(){
Expand Down Expand Up @@ -436,6 +489,7 @@ void loadRepositories(){
}

void fetch(Seq<String> repos){
Log.debug("Fetching schematics from repos: @", repos);
ui.showInfoFade("@schematic.browser.fetching", 2f);
for (String link : repos){
Http.get(ghApi + "/repos/" + link + "/zipball/main", res -> handleRedirect(link, res), e -> Core.app.post(() -> {
Expand Down Expand Up @@ -503,7 +557,7 @@ void setup(){
rebuild();
cont.pane( t -> {
t.defaults().pad(5f);
t.pane ( p -> p.add(repoTable)).growX();
t.pane(p -> p.add(repoTable)).growX();
});
}

Expand Down Expand Up @@ -593,7 +647,7 @@ void close(){
}
Core.settings.put("schematicrepositories", ui.schematicBrowser.repositoryLinks.toString(";"));
Core.settings.put("hiddenschematicrepositories", ui.schematicBrowser.hiddenRepositories.toString(";"));
this.hide();
hide();
}
}
}
2 changes: 1 addition & 1 deletion core/src/mindustry/core/UI.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ public class UI implements ApplicationListener, Loadable{
public PlanetDialog planet;
public ResearchDialog research;
public SchematicsDialog schematics;
public SchematicBrowserDialog schematicBrowser;
public ModsDialog mods;
public ColorPicker picker;
public EffectsDialog effects;
Expand All @@ -90,6 +89,7 @@ public class UI implements ApplicationListener, Loadable{
private @Nullable Element lastAnnouncement;

// Client related
public SchematicBrowserDialog schematicBrowser;
public UnitPicker unitPicker;
public ClajManagerDialog clajManager;
public ClajJoinDialog clajJoin;
Expand Down
4 changes: 3 additions & 1 deletion core/src/mindustry/game/Schematic.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@ public float powerConsumption(){
}

public ItemSeq requirements(){
ItemSeq requirements = new ItemSeq();
return requirements(new ItemSeq());
}

public ItemSeq requirements(ItemSeq requirements){
tiles.each(t -> {
for(ItemStack stack : t.block.requirements){
requirements.add(stack.item, stack.amount);
Expand Down
24 changes: 11 additions & 13 deletions core/src/mindustry/ui/dialogs/SchematicsDialog.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import kotlin.Unit;
import mindustry.client.*;
import mindustry.client.communication.*;
import mindustry.client.navigation.*;
Expand All @@ -40,7 +39,6 @@ public class SchematicsDialog extends BaseDialog{
private Pattern ignoreSymbols = Pattern.compile("[`~!@#$%^&*()\\-_=+{}|;:'\",<.>/?]");
private Seq<String> tags, selectedTags = new Seq<>();
private boolean checkedTags;
private static long previewTime;

public SchematicsDialog(){
super("@schematics");
Expand All @@ -55,7 +53,6 @@ public SchematicsDialog(){
makeButtonOverlay();
shown(this::setup);
onResize(this::setup);
update(() -> previewTime = 0);
}

void setup(){
Expand Down Expand Up @@ -308,14 +305,9 @@ public void showExport(Schematic s){
t.button("@schematic.chatshare", Icon.bookOpen, style, () -> {
if (!state.isPlaying()) return;
dialog.hide();
clientThread.post(() -> {
Main.INSTANCE.send(new SchematicTransmission(s), () -> {
Core.app.post(() -> {
ui.showInfoToast(Core.bundle.get("client.finisheduploading"), 2f);
});
return Unit.INSTANCE;
});
});
clientThread.post(() -> Main.INSTANCE.send(new SchematicTransmission(s), () -> Core.app.post(() ->
ui.showInfoToast(Core.bundle.get("client.finisheduploading"), 2f)
)));
}).marginLeft(12f).get().setDisabled(() -> !state.isPlaying());
});
});
Expand Down Expand Up @@ -730,6 +722,12 @@ public static class SchematicImage extends Image{
private Texture lastTexture;
boolean set;

private static long previewTime;

static { // Reset the preview time every frame
Events.run(EventType.Trigger.update, () -> previewTime = 0);
}

public SchematicImage(Schematic s){
super(Tex.clear);
setScaling(Scaling.fit);
Expand Down Expand Up @@ -778,14 +776,14 @@ public void draw(){
}

private void setPreview(){
if(Core.settings.getBool("restrictschematicloading", false) && previewTime > Time.millisToNanos(10) && !schematics.hasPreview(schematic)){ // Only allow 10ms of expensive preview creation each frame. Yes this is janky. No I don't care
if(Core.settings.getBool("restrictschematicloading", false) && previewTime > Time.millisToNanos(Core.settings.getInt("schemloadtime", 10)) && !schematics.hasPreview(schematic)){ // Only allow 10ms of expensive preview creation each frame. Yes this is janky. No I don't care
set = false;
return;
}
var start = Time.nanos();
TextureRegionDrawable draw = new TextureRegionDrawable(new TextureRegion(lastTexture = schematics.getPreview(schematic)));
var time = Time.timeSinceNanos(start);
if(time > Time.millisToNanos(100)) Log.info("Schematic @ (@x@) took @ms to load", schematic.name(), schematic.width, schematic.height, time/(float)Time.nanosPerMilli);
if(time > Time.millisToNanos(50)) Log.debug("Schematic @ (@x@) took @ms to load", schematic.name(), schematic.width, schematic.height, time/(float)Time.nanosPerMilli);
previewTime += time;
setDrawable(draw);
setScaling(Scaling.fit);
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ org.gradle.internal.http.connectionTimeout=100000
#kapt.verbose=true
# For some reason kapt ir is just completely broken for us. I don't know why.
kapt.use.jvm.ir=false
archash=2863938b5e8a4756428faa0327d7616dcbc8511b
archash=6ba46537648804942be923d32e678d49602f010e

0 comments on commit 8f52ef5

Please sign in to comment.