synchronized time-of-day
Some checks failed
studiorailgun/Renderer/pipeline/head There was a failure building this commit

This commit is contained in:
austin 2025-05-30 16:01:30 -04:00
parent 69234adb60
commit b70febb678
17 changed files with 322 additions and 25 deletions

View File

@ -2081,6 +2081,7 @@ Pathing construction for farm plots
Bounding sphere work Bounding sphere work
Don't allocate contact joints for geom-geom Don't allocate contact joints for geom-geom
Remove potential collision engine footgun Remove potential collision engine footgun
Synchronized time-of-day between server and client

View File

@ -10,6 +10,7 @@ import electrosphere.client.player.ClientPlayerData;
import electrosphere.client.scene.ClientLevelEditorData; import electrosphere.client.scene.ClientLevelEditorData;
import electrosphere.client.scene.ClientSceneWrapper; import electrosphere.client.scene.ClientSceneWrapper;
import electrosphere.client.scene.ClientWorldData; import electrosphere.client.scene.ClientWorldData;
import electrosphere.client.service.ClientTemporalService;
import electrosphere.client.sim.ClientSimulation; import electrosphere.client.sim.ClientSimulation;
import electrosphere.client.terrain.cells.ClientDrawCellManager; import electrosphere.client.terrain.cells.ClientDrawCellManager;
import electrosphere.client.terrain.foliage.FoliageCellManager; import electrosphere.client.terrain.foliage.FoliageCellManager;
@ -17,6 +18,7 @@ import electrosphere.client.terrain.manager.ClientTerrainManager;
import electrosphere.collision.CollisionEngine; import electrosphere.collision.CollisionEngine;
import electrosphere.data.entity.common.CommonEntityType; import electrosphere.data.entity.common.CommonEntityType;
import electrosphere.data.voxel.VoxelType; import electrosphere.data.voxel.VoxelType;
import electrosphere.engine.Globals;
import electrosphere.entity.Entity; import electrosphere.entity.Entity;
import electrosphere.entity.scene.Scene; import electrosphere.entity.scene.Scene;
import electrosphere.net.client.ClientNetworking; import electrosphere.net.client.ClientNetworking;
@ -178,11 +180,17 @@ public class ClientState {
*/ */
public int openInventoriesCount = 0; public int openInventoriesCount = 0;
//
//Services
//
public final ClientTemporalService clientTemporalService;
/** /**
* Constructor * Constructor
*/ */
public ClientState(){ public ClientState(){
this.clientSceneWrapper = new ClientSceneWrapper(this.clientScene, new CollisionEngine(), CollisionEngine.create(new ClientChemistryCollisionCallback()), new CollisionEngine()); this.clientSceneWrapper = new ClientSceneWrapper(this.clientScene, new CollisionEngine(), CollisionEngine.create(new ClientChemistryCollisionCallback()), new CollisionEngine());
this.clientTemporalService = (ClientTemporalService)Globals.engineState.serviceManager.registerService(new ClientTemporalService());
} }
} }

View File

@ -0,0 +1,64 @@
package electrosphere.client.service;
import electrosphere.engine.signal.Signal.SignalType;
import electrosphere.server.simulation.temporal.TemporalSimulator;
import electrosphere.util.math.BasicMathUtils;
import java.util.concurrent.locks.ReentrantLock;
import electrosphere.data.macro.temporal.MacroTemporalData;
import electrosphere.engine.signal.SignalServiceImpl;
/**
* Synchronizes and interpolates temporal data between server and client
*/
public class ClientTemporalService extends SignalServiceImpl {
/**
* Lerp rate for synchronization
*/
private static final double LERP_RATE = 1.0 / (double)TemporalSimulator.TEMPORAL_SYNC_RATE;
/**
* The client's stored temporal data
*/
private MacroTemporalData clientTemporalData = new MacroTemporalData();
/**
* The latest temporal data from the server
*/
private MacroTemporalData latestServerData = new MacroTemporalData();
/**
* Lock for thread-safeing the service
*/
private ReentrantLock lock = new ReentrantLock();
/**
* Constructor
*/
public ClientTemporalService() {
super("ClientTemporalService", new SignalType[]{
});
}
/**
* Simulates the service
*/
public void simulate(){
lock.lock();
clientTemporalData.setTime((long)BasicMathUtils.lerp((double)clientTemporalData.getTime(), (double)latestServerData.getTime(), LERP_RATE));
lock.unlock();
}
/**
* Sets the latest server data
* @param serverData The latest server data
*/
public void setLatestData(MacroTemporalData serverData){
lock.lock();
this.latestServerData = serverData;
lock.unlock();
}
}

View File

@ -152,10 +152,20 @@ public class ClientSimulation {
AttachUtils.clientUpdateAttachedEntityPositions(); AttachUtils.clientUpdateAttachedEntityPositions();
ClientInteractionEngine.updateInteractionTargetLabel(); ClientInteractionEngine.updateInteractionTargetLabel();
ToolbarPreviewWindow.checkVisibility(); ToolbarPreviewWindow.checkVisibility();
this.runServices();
// updateCellManager(); // updateCellManager();
Globals.profiler.endCpuSample(); Globals.profiler.endCpuSample();
} }
/**
* Runs the client services
*/
private void runServices(){
Globals.profiler.beginCpuSample("ClientSimulation.runServices");
Globals.clientState.clientTemporalService.simulate();
Globals.profiler.endCpuSample();
}
/** /**
* Updates the skybox position to center on the player * Updates the skybox position to center on the player

View File

@ -0,0 +1,64 @@
package electrosphere.data.macro.temporal;
/**
* Temporal data associated with the macro data (ie calendar date, world age, etc)
*/
public class MacroTemporalData {
/**
* Amount of time per day
*/
public static final long TIME_PER_DAY = 60 * 60 * 30;
/**
* The noon remainder amount
*/
public static final long TIME_NOON = TIME_PER_DAY / 2;
/**
* The midnight remainder amount
*/
public static final long TIME_MIDNIGHT = 0;
/**
* Total age of the macro data in years
*/
private long age;
/**
* The time WITHIN THE CURRENT YEAR
*/
private long time;
/**
* Increments the time of the temporal data by some amount
*/
public void increment(long amount){
this.time = this.time + amount;
}
/**
* Gets the time WITHIN THE CURRENT YEAR of the data
* @return The time
*/
public long getTime(){
return this.time;
}
/**
* Gets the age of the world in years
* @return The age of the world in years
*/
public long getAge(){
return age;
}
/**
* Sets the time of the temporal data
* @param time The time
*/
public void setTime(long time){
this.time = time;
}
}

View File

@ -4,9 +4,11 @@ import java.util.List;
import com.google.gson.Gson; import com.google.gson.Gson;
import electrosphere.data.macro.temporal.MacroTemporalData;
import electrosphere.engine.Globals; import electrosphere.engine.Globals;
import electrosphere.net.parser.net.message.LoreMessage; import electrosphere.net.parser.net.message.LoreMessage;
import electrosphere.net.template.ClientProtocolTemplate; import electrosphere.net.template.ClientProtocolTemplate;
import electrosphere.util.SerializationUtils;
public class LoreProtocol implements ClientProtocolTemplate<LoreMessage> { public class LoreProtocol implements ClientProtocolTemplate<LoreMessage> {
@ -18,15 +20,20 @@ public class LoreProtocol implements ClientProtocolTemplate<LoreMessage> {
@Override @Override
public void handleSyncMessage(LoreMessage message) { public void handleSyncMessage(LoreMessage message) {
switch(message.getMessageSubtype()){ switch(message.getMessageSubtype()){
case RESPONSERACES: case RESPONSERACES: {
//we get back the race list as a json array, deserialize, and push into type loader //we get back the race list as a json array, deserialize, and push into type loader
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
List<String> playableRaces = new Gson().fromJson(message.getdata(), List.class); List<String> playableRaces = new Gson().fromJson(message.getdata(), List.class);
Globals.gameConfigCurrent.getCreatureTypeLoader().loadPlayableRaces(playableRaces); Globals.gameConfigCurrent.getCreatureTypeLoader().loadPlayableRaces(playableRaces);
break; } break;
case REQUESTRACES: case TEMPORALUPDATE: {
MacroTemporalData temporalData = SerializationUtils.deserialize(message.getdata(), MacroTemporalData.class);
Globals.clientState.clientTemporalService.setLatestData(temporalData);
} break;
case REQUESTRACES: {
//silently ignore //silently ignore
break; } break;
} }
} }

View File

@ -15,6 +15,7 @@ public class LoreMessage extends NetworkMessage {
public enum LoreMessageType { public enum LoreMessageType {
REQUESTRACES, REQUESTRACES,
RESPONSERACES, RESPONSERACES,
TEMPORALUPDATE,
} }
/** /**
@ -107,6 +108,36 @@ public class LoreMessage extends NetworkMessage {
return rVal; return rVal;
} }
/**
* Parses a message of type TemporalUpdate
*/
public static LoreMessage parseTemporalUpdateMessage(ByteBuffer byteBuffer, MessagePool pool, Map<Short,BiConsumer<NetworkMessage,ByteBuffer>> customParserMap){
if(byteBuffer.remaining() < 4){
return null;
}
int lenAccumulator = 0;
int datalen = byteBuffer.getInt();
lenAccumulator = lenAccumulator + datalen;
if(byteBuffer.remaining() < 4 + lenAccumulator){
return null;
}
LoreMessage rVal = (LoreMessage)pool.get(MessageType.LORE_MESSAGE);
rVal.messageType = LoreMessageType.TEMPORALUPDATE;
if(datalen > 0){
rVal.setdata(ByteStreamUtils.popStringFromByteBuffer(byteBuffer, datalen));
}
return rVal;
}
/**
* Constructs a message of type TemporalUpdate
*/
public static LoreMessage constructTemporalUpdateMessage(String data){
LoreMessage rVal = new LoreMessage(LoreMessageType.TEMPORALUPDATE);
rVal.setdata(data);
return rVal;
}
@Deprecated @Deprecated
@Override @Override
void serialize(){ void serialize(){
@ -135,6 +166,21 @@ public class LoreMessage extends NetworkMessage {
rawBytes[6+i] = stringBytes[i]; rawBytes[6+i] = stringBytes[i];
} }
break; break;
case TEMPORALUPDATE:
rawBytes = new byte[2+4+data.length()];
//message header
rawBytes[0] = TypeBytes.MESSAGE_TYPE_LORE;
//entity messaage header
rawBytes[1] = TypeBytes.LORE_MESSAGE_TYPE_TEMPORALUPDATE;
intValues = ByteStreamUtils.serializeIntToBytes(data.length());
for(int i = 0; i < 4; i++){
rawBytes[2+i] = intValues[i];
}
stringBytes = data.getBytes();
for(int i = 0; i < data.length(); i++){
rawBytes[6+i] = stringBytes[i];
}
break;
} }
serialized = true; serialized = true;
} }
@ -163,6 +209,21 @@ public class LoreMessage extends NetworkMessage {
//Write variable length table in packet //Write variable length table in packet
ByteStreamUtils.writeInt(stream, data.getBytes().length); ByteStreamUtils.writeInt(stream, data.getBytes().length);
//
//Write body of packet
ByteStreamUtils.writeString(stream, data);
} break;
case TEMPORALUPDATE: {
//
//message header
stream.write(TypeBytes.MESSAGE_TYPE_LORE);
stream.write(TypeBytes.LORE_MESSAGE_TYPE_TEMPORALUPDATE);
//
//Write variable length table in packet
ByteStreamUtils.writeInt(stream, data.getBytes().length);
// //
//Write body of packet //Write body of packet
ByteStreamUtils.writeString(stream, data); ByteStreamUtils.writeString(stream, data);

View File

@ -126,6 +126,9 @@ public abstract class NetworkMessage {
case TypeBytes.LORE_MESSAGE_TYPE_RESPONSERACES: case TypeBytes.LORE_MESSAGE_TYPE_RESPONSERACES:
rVal = LoreMessage.parseResponseRacesMessage(byteBuffer,pool,customParserMap); rVal = LoreMessage.parseResponseRacesMessage(byteBuffer,pool,customParserMap);
break; break;
case TypeBytes.LORE_MESSAGE_TYPE_TEMPORALUPDATE:
rVal = LoreMessage.parseTemporalUpdateMessage(byteBuffer,pool,customParserMap);
break;
} }
break; break;
case TypeBytes.MESSAGE_TYPE_PLAYER: case TypeBytes.MESSAGE_TYPE_PLAYER:

View File

@ -48,6 +48,7 @@ public class TypeBytes {
*/ */
public static final byte LORE_MESSAGE_TYPE_REQUESTRACES = 0; public static final byte LORE_MESSAGE_TYPE_REQUESTRACES = 0;
public static final byte LORE_MESSAGE_TYPE_RESPONSERACES = 1; public static final byte LORE_MESSAGE_TYPE_RESPONSERACES = 1;
public static final byte LORE_MESSAGE_TYPE_TEMPORALUPDATE = 2;
/* /*
Lore packet sizes Lore packet sizes
*/ */

View File

@ -155,9 +155,7 @@ public class Server implements Runnable {
public void broadcastMessage(NetworkMessage message){ public void broadcastMessage(NetworkMessage message){
connectListLock.acquireUninterruptibly(); connectListLock.acquireUninterruptibly();
for(ServerConnectionHandler client : activeConnections){ for(ServerConnectionHandler client : activeConnections){
if(Globals.clientState.clientPlayer == null || client.playerID != Globals.clientState.clientPlayer.getId()){ client.addMessagetoOutgoingQueue(message);
client.addMessagetoOutgoingQueue(message);
}
} }
connectListLock.release(); connectListLock.release();
} }

View File

@ -22,6 +22,7 @@ public class LoreProtocol implements ServerProtocolTemplate<LoreMessage> {
connectionHandler.addMessagetoOutgoingQueue(LoreMessage.constructResponseRacesMessage(returnData)); connectionHandler.addMessagetoOutgoingQueue(LoreMessage.constructResponseRacesMessage(returnData));
return null; return null;
} }
case TEMPORALUPDATE:
case RESPONSERACES: case RESPONSERACES:
return message; return message;
} }
@ -33,6 +34,7 @@ public class LoreProtocol implements ServerProtocolTemplate<LoreMessage> {
switch(message.getMessageSubtype()){ switch(message.getMessageSubtype()){
case REQUESTRACES: case REQUESTRACES:
case RESPONSERACES: case RESPONSERACES:
case TEMPORALUPDATE:
//silently ignore //silently ignore
break; break;
} }

View File

@ -37,72 +37,67 @@ public class Realm {
/** /**
* The set containing all data cells loaded into this realm * The set containing all data cells loaded into this realm
*/ */
Set<ServerDataCell> loadedDataCells = new HashSet<ServerDataCell>(); private Set<ServerDataCell> loadedDataCells = new HashSet<ServerDataCell>();
/** /**
* this is the cell that all players loading into the game (via connection startup, death, etc) reside in * this is the cell that all players loading into the game (via connection startup, death, etc) reside in
*/ */
ServerDataCell loadingCell = new ServerDataCell(new Scene()); private ServerDataCell loadingCell = new ServerDataCell(new Scene());
/** /**
* The data cell that will contain in-inventory items * The data cell that will contain in-inventory items
*/ */
ServerDataCell inventoryCell = new ServerDataCell(new Scene()); private ServerDataCell inventoryCell = new ServerDataCell(new Scene());
/**
* resolver for entity -> data cell within this realm
*/
EntityDataCellMapper entityDataCellMapper;
/** /**
* provides functions for relating data cells to physical locations (eg creating cells, deleting cells, etc) * provides functions for relating data cells to physical locations (eg creating cells, deleting cells, etc)
*/ */
DataCellManager dataCellManager; private DataCellManager dataCellManager;
/** /**
* The pathfinding manager * The pathfinding manager
*/ */
PathfindingManager pathfindingManager; private PathfindingManager pathfindingManager;
/** /**
* Main entity physics collision checking engine * Main entity physics collision checking engine
*/ */
CollisionEngine collisionEngine; private CollisionEngine collisionEngine;
/** /**
* The chemistry collision engine * The chemistry collision engine
*/ */
CollisionEngine chemistryEngine; private CollisionEngine chemistryEngine;
/** /**
* Hitbox manager for the realm * Hitbox manager for the realm
*/ */
HitboxManager hitboxManager; private HitboxManager hitboxManager;
/** /**
* The world data about the server * The world data about the server
*/ */
ServerWorldData serverWorldData; private ServerWorldData serverWorldData;
/** /**
* The content manager * The content manager
*/ */
ServerContentManager serverContentManager; private ServerContentManager serverContentManager;
/** /**
* The macro data for the realm * The macro data for the realm
*/ */
MacroData macroData; private MacroData macroData;
/** /**
* The instanceId of the scene that was loaded with this realm * The instanceId of the scene that was loaded with this realm
*/ */
int sceneInstanceId = NO_SCENE_INSTANCE; private int sceneInstanceId = NO_SCENE_INSTANCE;
/** /**
* The list of available spawnpoints * The list of available spawnpoints
*/ */
List<Vector3d> spawnPoints = new LinkedList<Vector3d>(); private List<Vector3d> spawnPoints = new LinkedList<Vector3d>();
/** /**
* Realm constructor * Realm constructor

View File

@ -5,6 +5,7 @@ import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import electrosphere.data.macro.temporal.MacroTemporalData;
import electrosphere.engine.Globals; import electrosphere.engine.Globals;
import electrosphere.logger.LoggerInterface; import electrosphere.logger.LoggerInterface;
import electrosphere.server.datacell.ServerWorldData; import electrosphere.server.datacell.ServerWorldData;
@ -82,6 +83,11 @@ public class MacroData {
*/ */
private MacroPathCache pathingCache = new MacroPathCache(); private MacroPathCache pathingCache = new MacroPathCache();
/**
* The macro temporal data
*/
private MacroTemporalData temporalData = new MacroTemporalData();
/** /**
* Generates a world * Generates a world
* @param seed The seed for the world * @param seed The seed for the world
@ -396,5 +402,13 @@ public class MacroData {
public MacroPathCache getPathCache(){ public MacroPathCache getPathCache(){
return this.pathingCache; return this.pathingCache;
} }
/**
* Gets the temporal data
* @return The temporal data
*/
public MacroTemporalData getTemporalData(){
return this.temporalData;
}
} }

View File

@ -9,6 +9,7 @@ import electrosphere.server.macro.civilization.town.Town;
import electrosphere.server.macro.civilization.town.TownSimulator; import electrosphere.server.macro.civilization.town.TownSimulator;
import electrosphere.server.service.CharacterService; import electrosphere.server.service.CharacterService;
import electrosphere.server.simulation.chara.CharaSimulation; import electrosphere.server.simulation.chara.CharaSimulation;
import electrosphere.server.simulation.temporal.TemporalSimulator;
/** /**
* Performs the macro-level (ie virtual, non-physics based) simulation * Performs the macro-level (ie virtual, non-physics based) simulation
@ -53,6 +54,11 @@ public class MacroSimulation {
TownSimulator.simualte(town); TownSimulator.simualte(town);
} }
Globals.profiler.endCpuSample(); Globals.profiler.endCpuSample();
//
//temporal update
TemporalSimulator.simulate(realm);
Globals.profiler.endCpuSample(); Globals.profiler.endCpuSample();
} }

View File

@ -0,0 +1,37 @@
package electrosphere.server.simulation.temporal;
import electrosphere.data.macro.temporal.MacroTemporalData;
import electrosphere.engine.Globals;
import electrosphere.net.parser.net.message.LoreMessage;
import electrosphere.server.datacell.Realm;
import electrosphere.util.SerializationUtils;
/**
* Temporal macro data simulator
*/
public class TemporalSimulator {
/**
* Number of temporal ticks per sim frame
*/
private static final int TEMPORAL_TICKS_PER_SIM_FRAME = 1;
/**
* The rate at which to send synchronization packets to clients to update temporal data
*/
public static final int TEMPORAL_SYNC_RATE = 600;
/**
* Simulates the temporal macro data
* @param macroData The macro data
*/
public static void simulate(Realm realm){
MacroTemporalData temporalData = realm.getMacroData().getTemporalData();
temporalData.increment(TemporalSimulator.TEMPORAL_TICKS_PER_SIM_FRAME);
if(temporalData.getTime() % TEMPORAL_SYNC_RATE == 0){
String data = SerializationUtils.serialize(temporalData);
Globals.serverState.server.broadcastMessage(LoreMessage.constructTemporalUpdateMessage(data));
}
}
}

View File

@ -0,0 +1,19 @@
package electrosphere.util.math;
/**
* Basic math functions
*/
public class BasicMathUtils {
/**
* Linearly interpolates between two doubles
* @param a The first double
* @param b The second double
* @param percent The percentage to interpolate between them
* @return The interpolated value
*/
public static double lerp(double a, double b, double percent){
return a * (1.0 - percent) + (b * percent);
}
}

View File

@ -22,6 +22,13 @@
"data" : [ "data" : [
"data" "data"
] ]
},
{
"messageName" : "TemporalUpdate",
"description" : "Sends the current temporal data to the client",
"data" : [
"data"
]
} }
] ]
} }