Sync cleanup + player-local entities/labels

This commit is contained in:
Anuken
2026-07-04 11:18:07 -04:00
parent c756faddac
commit e3a877c948
6 changed files with 85 additions and 96 deletions
+56 -83
View File
@@ -114,11 +114,6 @@ public class NetServer implements ApplicationListener{
public static float voteDuration = 0.5f * 60; public static float voteDuration = 0.5f * 60;
/** Cooldown between votes in seconds. */ /** Cooldown between votes in seconds. */
public static int voteCooldown = 60 * 5; public static int voteCooldown = 60 * 5;
/**
* If this is true, isSyncHidden will called only once per team if FoW is enabled, or not at all.
* Set this to false if a mod uses isSyncHidden to hide entities.
*/
public boolean skipHiddenEntitiesCheck = true;
private ReusableByteOutStream writeBuffer = new ReusableByteOutStream(127); private ReusableByteOutStream writeBuffer = new ReusableByteOutStream(127);
private Writes outputBuffer = new Writes(new DataOutputStream(writeBuffer)); private Writes outputBuffer = new Writes(new DataOutputStream(writeBuffer));
@@ -1116,48 +1111,6 @@ public class NetServer implements ApplicationListener{
universe.seconds(), tps, GlobalVars.rand.seed0, GlobalVars.rand.seed1, syncStream.toByteArray()); universe.seconds(), tps, GlobalVars.rand.seed0, GlobalVars.rand.seed1, syncStream.toByteArray());
} }
public void writeEntitySnapshot(Player player) throws IOException{
syncStream.reset();
hiddenIds.clear();
int sent = 0;
for(Syncc entity : Groups.sync){
//TODO write to special list
if(entity.isSyncHidden(player)){
hiddenIds.add(entity.id());
continue;
}
//write all entities now
dataStream.writeInt(entity.id()); //write id
dataStream.writeByte(entity.classId() & 0xFF); //write type ID
entity.beforeWrite();
entity.writeSync(dataStreamWrites); //write entity itself
sent++;
if(syncStream.size() > maxSnapshotSize){
dataStream.close();
Call.entitySnapshot(player.con, (short)sent, syncStream.toByteArray());
sent = 0;
syncStream.reset();
}
}
if(sent > 0){
dataStream.close();
Call.entitySnapshot(player.con, (short)sent, syncStream.toByteArray());
}
if(hiddenIds.size > 0){
Call.hiddenSnapshot(player.con, hiddenIds);
}
player.con.snapshotsSent++;
}
/** Does not check isSyncHidden. Call this if no entities are hidden. */ /** Does not check isSyncHidden. Call this if no entities are hidden. */
public void writeEntitySnapshotsAll() throws IOException{ public void writeEntitySnapshotsAll() throws IOException{
syncStream.reset(); syncStream.reset();
@@ -1165,11 +1118,7 @@ public class NetServer implements ApplicationListener{
int sent = 0; int sent = 0;
for(Syncc entity : Groups.sync){ for(Syncc entity : Groups.sync){
//write all entities now writeEntity(entity, dataStream);
dataStream.writeInt(entity.id()); //write id
dataStream.writeByte(entity.classId() & 0xFF); //write type ID
entity.beforeWrite();
entity.writeSync(dataStreamWrites); //write entity itself
sent++; sent++;
@@ -1191,30 +1140,25 @@ public class NetServer implements ApplicationListener{
} }
/** Checks isSyncHidden for only one player per team. Called if FoW is enabled but there is no custom syncHidden. */ /** Checks isSyncHidden for only one player per team. Called if FoW is enabled but there is no custom syncHidden. */
public void writeEntitySnapshotsTeam(Seq<Player> players) throws IOException{ public void writeEntitySnapshotsTeam(Team team, Seq<Player> players) throws IOException{
syncStream.reset(); syncStream.reset();
hiddenIds.clear(); hiddenIds.clear();
int sent = 0; int sent = 0;
for(Syncc entity : Groups.sync){ for(Syncc entity : Groups.sync){
//TODO write to special list if(entity.isSyncHidden(team)){
if(entity.isSyncHidden(players.first())){
hiddenIds.add(entity.id()); hiddenIds.add(entity.id());
continue; continue;
} }
//write all entities now writeEntity(entity, dataStream);
dataStream.writeInt(entity.id()); //write id
dataStream.writeByte(entity.classId() & 0xFF); //write type ID
entity.beforeWrite();
entity.writeSync(dataStreamWrites); //write entity itself
sent++; sent++;
if(syncStream.size() > maxSnapshotSize){ if(syncStream.size() > maxSnapshotSize){
dataStream.close(); dataStream.close();
final var ssent = (short)sent; short ssent = (short)sent;
var bytes = syncStream.toByteArray(); var bytes = syncStream.toByteArray();
players.each(player -> Call.entitySnapshot(player.con, ssent, bytes)); players.each(player -> Call.entitySnapshot(player.con, ssent, bytes));
sent = 0; sent = 0;
@@ -1224,7 +1168,7 @@ public class NetServer implements ApplicationListener{
if(sent > 0){ if(sent > 0){
dataStream.close(); dataStream.close();
final var ssent = (short)sent; short ssent = (short)sent;
var bytes = syncStream.toByteArray(); var bytes = syncStream.toByteArray();
players.each(player -> Call.entitySnapshot(player.con, ssent, bytes)); players.each(player -> Call.entitySnapshot(player.con, ssent, bytes));
} }
@@ -1236,6 +1180,40 @@ public class NetServer implements ApplicationListener{
players.each(player -> player.con.snapshotsSent++); players.each(player -> player.con.snapshotsSent++);
} }
/** Writes a custom snapshot containing player-local entities; this is for entities other players don't see. */
public void writeCustomEntitySnapshot(Player player, Iterable<Syncc> entities) throws IOException{
syncStream.reset();
int sent = 0;
for(Syncc entity : entities){
writeEntity(entity, dataStream);
sent++;
if(syncStream.size() > maxSnapshotSize){
dataStream.close();
Call.entitySnapshot(player.con, (short)sent, syncStream.toByteArray());
sent = 0;
syncStream.reset();
}
}
if(sent > 0){
dataStream.close();
Call.entitySnapshot(player.con, (short)sent, syncStream.toByteArray());
}
}
protected void writeEntity(Syncc entity, DataOutputStream dataStream) throws IOException{
dataStream.writeInt(entity.id()); //write id
dataStream.writeByte(entity.classId() & 0xFF); //write type ID
entity.beforeWrite();
entity.writeSync(dataStreamWrites); //write entity itself
}
public String fixName(String name){ public String fixName(String name){
name = name.trim().replace("\n", "").replace("\t", ""); name = name.trim().replace("\n", "").replace("\t", "");
if(name.equals("[") || name.equals("]")){ if(name.equals("[") || name.equals("]")){
@@ -1291,7 +1269,6 @@ public class NetServer implements ApplicationListener{
Groups.player.each(p -> !p.isLocal(), player -> { Groups.player.each(p -> !p.isLocal(), player -> {
if(player.con == null || !player.con.isConnected()){ if(player.con == null || !player.con.isConnected()){
onDisconnect(player, "disappeared"); onDisconnect(player, "disappeared");
return;
} }
}); });
@@ -1300,29 +1277,25 @@ public class NetServer implements ApplicationListener{
writeStateSnapshot(); writeStateSnapshot();
if(skipHiddenEntitiesCheck){ if(Vars.state.rules.fog){
if(Vars.state.rules.fog){ //Serialize by teams
//Serialize by teams for(Team team : Team.all){ //Not Teams.active, because players can be on inactive teams
for(Team team : Team.all){ //Not Teams.active, because players can be on inactive teams var tdata = team.data();
var tdata = team.data(); playersToSend.selectFrom(tdata.players, p -> !p.isLocal() && p.con.hasConnected);
playersToSend.selectFrom(tdata.players, p -> !p.isLocal() && p.con.hasConnected); if(!playersToSend.isEmpty()){
if(!playersToSend.isEmpty()){ writeEntitySnapshotsTeam(team, playersToSend);
writeEntitySnapshotsTeam(playersToSend);
}
} }
}else{
//Serialize once for all players
writeEntitySnapshotsAll();
} }
}else{ }else{
//Serialize for each player //Serialize once for all players
Groups.player.each(p -> !p.isLocal() && p.con.hasConnected, player -> { writeEntitySnapshotsAll();
try{ }
writeEntitySnapshot(player);
}catch(IOException e){ //write custom player-specific entities (usually labels)
Log.err(e); for(Player player : Groups.player){
} if(player.con != null && player.con.hasConnected && player.con.localEntities.size > 0){
}); writeCustomEntitySnapshot(player, player.con.localEntities);
}
} }
} }
@@ -3,6 +3,7 @@ package mindustry.entities.comp;
import arc.util.io.*; import arc.util.io.*;
import mindustry.*; import mindustry.*;
import mindustry.annotations.Annotations.*; import mindustry.annotations.Annotations.*;
import mindustry.game.*;
import mindustry.gen.*; import mindustry.gen.*;
import java.nio.*; import java.nio.*;
@@ -21,7 +22,7 @@ abstract class SyncComp implements Entityc{
void afterSync(){} void afterSync(){}
void interpolate(){} void interpolate(){}
boolean isSyncHidden(Player player){ boolean isSyncHidden(Team team){
return false; return false;
} }
@@ -213,9 +213,9 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
@Override @Override
@Replace @Replace
public boolean isSyncHidden(Player player){ public boolean isSyncHidden(Team team){
//shooting reveals position so bullets can be seen //shooting reveals position so bullets can be seen
return !isShooting() && inFogTo(player.team()); return !isShooting() && inFogTo(team);
} }
@Override @Override
@@ -17,14 +17,14 @@ import mindustry.ui.*;
public abstract class WorldLabelComp implements Posc, Drawc, Syncc{ public abstract class WorldLabelComp implements Posc, Drawc, Syncc{
@Import int id; @Import int id;
@Import float x, y; @Import float x, y;
@Import boolean added;
public static final byte public static final byte
flagBackground = 1, flagBackground = 1 << 0,
flagOutline = 2, flagOutline = 1 << 1,
flagAlignLeft = 4, flagAlignLeft = 1 << 2,
flagAlignRight = 8, flagAlignRight = 1 << 3,
flagAutoscale = 16, flagAutoscale = 1 << 4;
flagOnlyParentVisible = 32;
public String text = "sample text"; public String text = "sample text";
public float fontSize = 1f, z = Layer.playerName + 1; public float fontSize = 1f, z = Layer.playerName + 1;
@@ -37,6 +37,7 @@ public abstract class WorldLabelComp implements Posc, Drawc, Syncc{
@Replace @Replace
public float clipSize(){ public float clipSize(){
if(parent != null) return Float.MAX_VALUE;
return text.length() * 10f * fontSize; return text.length() * 10f * fontSize;
} }
@@ -54,9 +55,6 @@ public abstract class WorldLabelComp implements Posc, Drawc, Syncc{
if(parent != null){ if(parent != null){
x += parent.x(); x += parent.x();
y += parent.y(); y += parent.y();
if((flags & flagOnlyParentVisible) != 0 && !parent.isLocal()){
return;
}
} }
drawAt(text, x, y, z, flags, fontSize, Align.center, (flags & flagAlignLeft) != 0 ? Align.left : (flags & flagAlignRight) != 0 ? Align.right : Align.center); drawAt(text, x, y, z, flags, fontSize, Align.center, (flags & flagAlignLeft) != 0 ? Align.left : (flags & flagAlignRight) != 0 ? Align.right : Align.center);
} }
@@ -111,6 +109,21 @@ public abstract class WorldLabelComp implements Posc, Drawc, Syncc{
Draw.z(z); Draw.z(z);
} }
/** Makes this label visible only to the specific player. This must be called instead of add(). */
public void show(Player player){
if(added || player.con == null) return;
player.con.localEntities.add(this);
added = true;
}
/** Hides this player-specific label. If you used {@link #show(Player)} previously, you must call this method instead of {@link #hide()}! */
public void hide(Player player){
if(!added || player.con == null) return;
player.con.localEntities.remove(this);
Call.removeWorldLabel(player.con, id);
added = false;
}
/** This MUST be called instead of remove()! */ /** This MUST be called instead of remove()! */
public void hide(){ public void hide(){
remove(); remove();
@@ -31,6 +31,8 @@ public abstract class NetConnection{
public Ratekeeper chatRate = new Ratekeeper(); public Ratekeeper chatRate = new Ratekeeper();
/** Handles packet spam rate limits. */ /** Handles packet spam rate limits. */
public Ratekeeper packetRate = new Ratekeeper(); public Ratekeeper packetRate = new Ratekeeper();
/** Entities that only this player will get synced to them. */
public Seq<Syncc> localEntities = new Seq<>(false);
//TODO: refactor to state enum //TODO: refactor to state enum
public boolean hasConnected, hasBegunConnecting, determiningAssets, receivingAssets, hasDisconnected; public boolean hasConnected, hasBegunConnecting, determiningAssets, receivingAssets, hasDisconnected;
+1 -1
View File
@@ -200,7 +200,7 @@ public class Menus{
} }
//internal use only //internal use only
@Remote @Remote(variants = Variant.both)
public static void removeWorldLabel(int id){ public static void removeWorldLabel(int id){
var label = Groups.label.getByID(id); var label = Groups.label.getByID(id);
if(label != null){ if(label != null){