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
Don't allocate contact joints for geom-geom
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.ClientSceneWrapper;
import electrosphere.client.scene.ClientWorldData;
import electrosphere.client.service.ClientTemporalService;
import electrosphere.client.sim.ClientSimulation;
import electrosphere.client.terrain.cells.ClientDrawCellManager;
import electrosphere.client.terrain.foliage.FoliageCellManager;
@ -17,6 +18,7 @@ import electrosphere.client.terrain.manager.ClientTerrainManager;
import electrosphere.collision.CollisionEngine;
import electrosphere.data.entity.common.CommonEntityType;
import electrosphere.data.voxel.VoxelType;
import electrosphere.engine.Globals;
import electrosphere.entity.Entity;
import electrosphere.entity.scene.Scene;
import electrosphere.net.client.ClientNetworking;
@ -178,11 +180,17 @@ public class ClientState {
*/
public int openInventoriesCount = 0;
//
//Services
//
public final ClientTemporalService clientTemporalService;
/**
* Constructor
*/
public ClientState(){
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();
ClientInteractionEngine.updateInteractionTargetLabel();
ToolbarPreviewWindow.checkVisibility();
this.runServices();
// updateCellManager();
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

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

View File

@ -15,6 +15,7 @@ public class LoreMessage extends NetworkMessage {
public enum LoreMessageType {
REQUESTRACES,
RESPONSERACES,
TEMPORALUPDATE,
}
/**
@ -107,6 +108,36 @@ public class LoreMessage extends NetworkMessage {
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
@Override
void serialize(){
@ -135,6 +166,21 @@ public class LoreMessage extends NetworkMessage {
rawBytes[6+i] = stringBytes[i];
}
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;
}
@ -163,6 +209,21 @@ public class LoreMessage extends NetworkMessage {
//Write variable length table in packet
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
ByteStreamUtils.writeString(stream, data);

View File

@ -126,6 +126,9 @@ public abstract class NetworkMessage {
case TypeBytes.LORE_MESSAGE_TYPE_RESPONSERACES:
rVal = LoreMessage.parseResponseRacesMessage(byteBuffer,pool,customParserMap);
break;
case TypeBytes.LORE_MESSAGE_TYPE_TEMPORALUPDATE:
rVal = LoreMessage.parseTemporalUpdateMessage(byteBuffer,pool,customParserMap);
break;
}
break;
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_RESPONSERACES = 1;
public static final byte LORE_MESSAGE_TYPE_TEMPORALUPDATE = 2;
/*
Lore packet sizes
*/

View File

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

View File

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

View File

@ -37,72 +37,67 @@ public class 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
*/
ServerDataCell loadingCell = new ServerDataCell(new Scene());
private ServerDataCell loadingCell = new ServerDataCell(new Scene());
/**
* The data cell that will contain in-inventory items
*/
ServerDataCell inventoryCell = new ServerDataCell(new Scene());
/**
* resolver for entity -> data cell within this realm
*/
EntityDataCellMapper entityDataCellMapper;
private ServerDataCell inventoryCell = new ServerDataCell(new Scene());
/**
* provides functions for relating data cells to physical locations (eg creating cells, deleting cells, etc)
*/
DataCellManager dataCellManager;
private DataCellManager dataCellManager;
/**
* The pathfinding manager
*/
PathfindingManager pathfindingManager;
private PathfindingManager pathfindingManager;
/**
* Main entity physics collision checking engine
*/
CollisionEngine collisionEngine;
private CollisionEngine collisionEngine;
/**
* The chemistry collision engine
*/
CollisionEngine chemistryEngine;
private CollisionEngine chemistryEngine;
/**
* Hitbox manager for the realm
*/
HitboxManager hitboxManager;
private HitboxManager hitboxManager;
/**
* The world data about the server
*/
ServerWorldData serverWorldData;
private ServerWorldData serverWorldData;
/**
* The content manager
*/
ServerContentManager serverContentManager;
private ServerContentManager serverContentManager;
/**
* The macro data for the realm
*/
MacroData macroData;
private MacroData macroData;
/**
* 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
*/
List<Vector3d> spawnPoints = new LinkedList<Vector3d>();
private List<Vector3d> spawnPoints = new LinkedList<Vector3d>();
/**
* Realm constructor

View File

@ -5,6 +5,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import electrosphere.data.macro.temporal.MacroTemporalData;
import electrosphere.engine.Globals;
import electrosphere.logger.LoggerInterface;
import electrosphere.server.datacell.ServerWorldData;
@ -82,6 +83,11 @@ public class MacroData {
*/
private MacroPathCache pathingCache = new MacroPathCache();
/**
* The macro temporal data
*/
private MacroTemporalData temporalData = new MacroTemporalData();
/**
* Generates a world
* @param seed The seed for the world
@ -397,4 +403,12 @@ public class MacroData {
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.service.CharacterService;
import electrosphere.server.simulation.chara.CharaSimulation;
import electrosphere.server.simulation.temporal.TemporalSimulator;
/**
* Performs the macro-level (ie virtual, non-physics based) simulation
@ -53,6 +54,11 @@ public class MacroSimulation {
TownSimulator.simualte(town);
}
Globals.profiler.endCpuSample();
//
//temporal update
TemporalSimulator.simulate(realm);
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"
]
},
{
"messageName" : "TemporalUpdate",
"description" : "Sends the current temporal data to the client",
"data" : [
"data"
]
}
]
}