Merge branch 'save-world'

This commit is contained in:
OLEGSHA 2021-09-11 19:42:36 +03:00
commit 409bbdb680
Signed by: OLEGSHA
GPG Key ID: E57A4B08D64AFF7A
34 changed files with 1357 additions and 347 deletions

View File

@ -18,18 +18,28 @@
package ru.windcorp.progressia; package ru.windcorp.progressia;
import ru.windcorp.progressia.client.graphics.GUI;
import ru.windcorp.progressia.common.util.crash.CrashReports; import ru.windcorp.progressia.common.util.crash.CrashReports;
import ru.windcorp.progressia.common.util.crash.analyzers.OutOfMemoryAnalyzer; import ru.windcorp.progressia.common.util.crash.analyzers.OutOfMemoryAnalyzer;
import ru.windcorp.progressia.common.util.crash.providers.*; import ru.windcorp.progressia.common.util.crash.providers.*;
import ru.windcorp.progressia.test.LayerTitle;
public class ProgressiaLauncher { public class ProgressiaLauncher {
public static String[] arguments; public static String[] arguments;
private static Proxy proxy;
public static void launch(String[] args, Proxy proxy) { public static void launch(String[] args, Proxy proxy) {
arguments = args.clone(); arguments = args.clone();
setupCrashReports(); setupCrashReports();
proxy.initialize(); proxy.initialize();
ProgressiaLauncher.proxy = proxy;
GUI.addTopLayer(new LayerTitle("Title"));
}
public static Proxy getProxy() {
return proxy;
} }
private static void setupCrashReports() { private static void setupCrashReports() {

View File

@ -30,7 +30,6 @@ import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
import ru.windcorp.progressia.client.localization.Localizer; import ru.windcorp.progressia.client.localization.Localizer;
import ru.windcorp.progressia.common.resource.ResourceManager; import ru.windcorp.progressia.common.resource.ResourceManager;
import ru.windcorp.progressia.common.util.crash.CrashReports; import ru.windcorp.progressia.common.util.crash.CrashReports;
import ru.windcorp.progressia.server.ServerState;
import ru.windcorp.progressia.test.TestContent; import ru.windcorp.progressia.test.TestContent;
import ru.windcorp.progressia.test.TestMusicPlayer; import ru.windcorp.progressia.test.TestMusicPlayer;
@ -38,7 +37,9 @@ public class ClientProxy implements Proxy {
@Override @Override
public void initialize() { public void initialize() {
GraphicsBackend.initialize(); GraphicsBackend.initialize();
try { try {
RenderTaskQueue.waitAndInvoke(FlatRenderProgram::init); RenderTaskQueue.waitAndInvoke(FlatRenderProgram::init);
RenderTaskQueue.waitAndInvoke(WorldRenderProgram::init); RenderTaskQueue.waitAndInvoke(WorldRenderProgram::init);
@ -58,10 +59,6 @@ public class ClientProxy implements Proxy {
AudioSystem.initialize(); AudioSystem.initialize();
ServerState.startServer();
ClientState.connectToLocalServer();
TestMusicPlayer.start(); TestMusicPlayer.start();
} }
} }

View File

@ -20,10 +20,13 @@ package ru.windcorp.progressia.client;
import ru.windcorp.progressia.client.comms.localhost.LocalServerCommsChannel; import ru.windcorp.progressia.client.comms.localhost.LocalServerCommsChannel;
import ru.windcorp.progressia.client.graphics.GUI; import ru.windcorp.progressia.client.graphics.GUI;
import ru.windcorp.progressia.client.graphics.Layer;
import ru.windcorp.progressia.client.graphics.world.LayerWorld; import ru.windcorp.progressia.client.graphics.world.LayerWorld;
import ru.windcorp.progressia.common.world.DefaultWorldData; import ru.windcorp.progressia.common.world.DefaultWorldData;
import ru.windcorp.progressia.client.localization.MutableStringLocalized;
import ru.windcorp.progressia.server.ServerState; import ru.windcorp.progressia.server.ServerState;
import ru.windcorp.progressia.test.LayerAbout; import ru.windcorp.progressia.test.LayerAbout;
import ru.windcorp.progressia.test.LayerTestText;
import ru.windcorp.progressia.test.LayerTestUI; import ru.windcorp.progressia.test.LayerTestUI;
import ru.windcorp.progressia.test.TestContent; import ru.windcorp.progressia.test.TestContent;
@ -52,11 +55,39 @@ public class ClientState {
channel.connect(TestContent.PLAYER_LOGIN); channel.connect(TestContent.PLAYER_LOGIN);
setInstance(client); setInstance(client);
displayLoadingScreen();
GUI.addBottomLayer(new LayerWorld(client)); }
GUI.addTopLayer(new LayerTestUI());
GUI.addTopLayer(new LayerAbout());
private static void displayLoadingScreen() {
GUI.addTopLayer(new LayerTestText("Text", new MutableStringLocalized("LayerText.Load"), layer -> {
Client client = ClientState.getInstance();
// TODO refacetor and remove
if (client != null) {
client.getComms().processPackets();
}
if (client != null && client.getLocalPlayer().hasEntity()) {
GUI.removeLayer(layer);
// TODO refactor, this shouldn't be here
LayerWorld layerWorld = new LayerWorld(client);
LayerTestUI layerUI = new LayerTestUI();
LayerAbout layerAbout = new LayerAbout();
GUI.addBottomLayer(layerWorld);
GUI.addTopLayer(layerUI);
GUI.addTopLayer(layerAbout);
}
}));
}
public static void disconnectFromLocalServer() {
getInstance().getComms().disconnect();
for (Layer layer : GUI.getLayers()) {
GUI.removeLayer(layer);
}
} }
private ClientState() { private ClientState() {

View File

@ -21,6 +21,7 @@ package ru.windcorp.progressia.client.comms.localhost;
import ru.windcorp.progressia.client.comms.ServerCommsChannel; import ru.windcorp.progressia.client.comms.ServerCommsChannel;
import ru.windcorp.progressia.common.comms.packets.Packet; import ru.windcorp.progressia.common.comms.packets.Packet;
import ru.windcorp.progressia.server.Server; import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.server.ServerState;
public class LocalServerCommsChannel extends ServerCommsChannel { public class LocalServerCommsChannel extends ServerCommsChannel {
@ -54,7 +55,7 @@ public class LocalServerCommsChannel extends ServerCommsChannel {
@Override @Override
public void disconnect() { public void disconnect() {
// Do nothing ServerState.getInstance().getClientManager().disconnectClient(localClient);
} }
} }

View File

@ -21,6 +21,7 @@ package ru.windcorp.progressia.client.graphics;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects;
import com.google.common.eventbus.Subscribe; import com.google.common.eventbus.Subscribe;
@ -58,6 +59,7 @@ public class GUI {
} }
public static void addBottomLayer(Layer layer) { public static void addBottomLayer(Layer layer) {
Objects.requireNonNull(layer, "layer");
modify(layers -> { modify(layers -> {
layers.add(layer); layers.add(layer);
layer.onAdded(); layer.onAdded();
@ -65,6 +67,7 @@ public class GUI {
} }
public static void addTopLayer(Layer layer) { public static void addTopLayer(Layer layer) {
Objects.requireNonNull(layer, "layer");
modify(layers -> { modify(layers -> {
layers.add(0, layer); layers.add(0, layer);
layer.onAdded(); layer.onAdded();
@ -72,6 +75,7 @@ public class GUI {
} }
public static void removeLayer(Layer layer) { public static void removeLayer(Layer layer) {
Objects.requireNonNull(layer, "layer");
modify(layers -> { modify(layers -> {
layers.remove(layer); layers.remove(layer);
layer.onRemoved(); layer.onRemoved();

View File

@ -43,9 +43,9 @@ public abstract class BasicButton extends Component {
private boolean isPressed = false; private boolean isPressed = false;
private final Collection<Consumer<BasicButton>> actions = Collections.synchronizedCollection(new ArrayList<>()); private final Collection<Consumer<BasicButton>> actions = Collections.synchronizedCollection(new ArrayList<>());
public BasicButton(String name, String label, Font labelFont) { public BasicButton(String name, Label label) {
super(name); super(name);
this.label = new Label(name + ".Label", labelFont, label); this.label = label;
setLayout(new LayoutAlign(10)); setLayout(new LayoutAlign(10));
addChild(this.label); addChild(this.label);
@ -104,6 +104,10 @@ public abstract class BasicButton extends Component {
}); });
} }
public BasicButton(String name, String label, Font labelFont) {
this(name, new Label(name + ".Label", labelFont, label));
}
public BasicButton(String name, String label) { public BasicButton(String name, String label) {
this(name, label, new Font()); this(name, label, new Font());
} }

View File

@ -29,6 +29,10 @@ public class Button extends BasicButton {
super(name, label, labelFont); super(name, label, labelFont);
} }
public Button(String name, Label label) {
super(name, label);
}
public Button(String name, String label) { public Button(String name, String label) {
this(name, label, new Font()); this(name, label, new Font());
} }

View File

@ -105,7 +105,8 @@ public class RadioButton extends BasicButton {
addChild(group); addChild(group);
addListener(KeyEvent.class, e -> { addListener(KeyEvent.class, e -> {
if (e.isRelease()) return false; if (e.isRelease())
return false;
if (e.getKey() == GLFW.GLFW_KEY_LEFT || e.getKey() == GLFW.GLFW_KEY_UP) { if (e.getKey() == GLFW.GLFW_KEY_LEFT || e.getKey() == GLFW.GLFW_KEY_UP) {
if (this.group != null) { if (this.group != null) {
@ -149,7 +150,8 @@ public class RadioButton extends BasicButton {
group.selectNext(); group.selectNext();
removeAction(group.listener); removeAction(group.listener);
group.buttons.remove(this); group.buttons.remove(this);
group.getSelected(); // Clear reference if this was the only button in the group group.getSelected(); // Clear reference if this was the only button
// in the group
} }
this.group = group; this.group = group;
@ -178,7 +180,8 @@ public class RadioButton extends BasicButton {
this.checked = checked; this.checked = checked;
if (group != null) { if (group != null) {
group.listener.accept(this); // Failsafe for manual invocations of setChecked() group.listener.accept(this); // Failsafe for manual invocations of
// setChecked()
} }
} }

View File

@ -163,4 +163,71 @@ public class Coordinates {
return blockInChunk == 0 || blockInChunk == BLOCKS_PER_CHUNK - 1; return blockInChunk == 0 || blockInChunk == BLOCKS_PER_CHUNK - 1;
} }
/*
* Generalized versions
*/
public static int convertGlobalToCell(int bits, int global) {
return global >> bits;
}
public static Vec3i convertGlobalToCell(
int bits,
Vec3i global,
Vec3i output
) {
if (output == null)
output = new Vec3i();
output.x = convertGlobalToCell(bits, global.x);
output.y = convertGlobalToCell(bits, global.y);
output.z = convertGlobalToCell(bits, global.z);
return output;
}
public static int convertGlobalToInCell(int bits, int global) {
int mask = (1 << bits) - 1;
return global & mask;
}
public static Vec3i convertGlobalToInCell(
int bits,
Vec3i global,
Vec3i output
) {
if (output == null)
output = new Vec3i();
output.x = convertGlobalToInCell(bits, global.x);
output.y = convertGlobalToInCell(bits, global.y);
output.z = convertGlobalToInCell(bits, global.z);
return output;
}
public static int getGlobal(int bits, int cell, int inCell) {
return inCell | (cell << bits);
}
public static Vec3i getGlobal(
int bits,
Vec3i cell,
Vec3i inCell,
Vec3i output
) {
if (output == null)
output = new Vec3i();
output.x = getGlobal(bits, cell.x, inCell.x);
output.y = getGlobal(bits, cell.y, inCell.y);
output.z = getGlobal(bits, cell.z, inCell.z);
return output;
}
public static boolean isOnCellBorder(int bits, int inCell) {
return inCell == 0 || inCell == (1 << bits) - 1;
}
} }

View File

@ -18,17 +18,18 @@
package ru.windcorp.progressia.server; package ru.windcorp.progressia.server;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.common.world.entity.EntityDataRegistry;
import ru.windcorp.progressia.server.comms.ClientPlayer;
import ru.windcorp.progressia.server.events.PlayerJoinedEvent;
import ru.windcorp.progressia.server.events.PlayerLeftEvent;
import ru.windcorp.progressia.test.TestContent;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.common.util.crash.CrashReports;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.common.world.entity.EntityDataRegistry;
import ru.windcorp.progressia.server.events.PlayerJoinedEvent;
import ru.windcorp.progressia.test.TestContent;
public class PlayerManager { public class PlayerManager {
private final Server server; private final Server server;
@ -48,17 +49,26 @@ public class PlayerManager {
getServer().postEvent(new PlayerJoinedEvent.Immutable(getServer(), player)); getServer().postEvent(new PlayerJoinedEvent.Immutable(getServer(), player));
} }
public EntityData conjurePlayerEntity(String login) { public void removePlayer(Player player) {
// TODO Live up to the name server.getWorld().getContainer().savePlayer(player, server);
if (TestContent.PLAYER_LOGIN.equals(login)) { this.players.remove(player);
EntityData entity = spawnPlayerEntity(login); getServer().postEvent(new PlayerLeftEvent.Immutable(getServer(), player));
return entity;
} else {
throw CrashReports.report(null, "Unknown login %s, javahorse stupid", login);
}
} }
private EntityData spawnPlayerEntity(String login) { public Player conjurePlayer(ClientPlayer clientPlayer, String login) {
Player player = getServer().getWorld().getContainer().loadPlayer(login, clientPlayer, getServer());
if (player == null) { // create new player
EntityData entity = spawnPlayerEntity(clientPlayer, login);
player = new Player(entity, getServer(), clientPlayer);
}
getServer().getWorld().getData().addEntity(player.getEntity());
return player;
}
private EntityData spawnPlayerEntity(ClientPlayer clientPlayer, String login) {
EntityData player = EntityDataRegistry.getInstance().create("Test:Player"); EntityData player = EntityDataRegistry.getInstance().create("Test:Player");
player.setEntityId(TestContent.PLAYER_ENTITY_ID); player.setEntityId(TestContent.PLAYER_ENTITY_ID);
@ -67,8 +77,6 @@ public class PlayerManager {
player.setUpVector(new Vec3(0, 0, 1)); player.setUpVector(new Vec3(0, 0, 1));
player.setLookingAt(new Vec3(2, 1, 0)); player.setLookingAt(new Vec3(2, 1, 0));
getServer().getWorld().getData().addEntity(player);
return player; return player;
} }

View File

@ -21,8 +21,6 @@ package ru.windcorp.progressia.server;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import org.apache.logging.log4j.LogManager;
import com.google.common.eventbus.EventBus; import com.google.common.eventbus.EventBus;
import glm.vec._3.i.Vec3i; import glm.vec._3.i.Vec3i;
@ -46,6 +44,7 @@ import ru.windcorp.progressia.server.world.context.impl.DefaultServerContext;
import ru.windcorp.progressia.server.world.context.impl.ReportingServerContext; import ru.windcorp.progressia.server.world.context.impl.ReportingServerContext;
import ru.windcorp.progressia.server.world.context.impl.RotatingServerContext; import ru.windcorp.progressia.server.world.context.impl.RotatingServerContext;
import ru.windcorp.progressia.server.world.generation.WorldGenerator; import ru.windcorp.progressia.server.world.generation.WorldGenerator;
import ru.windcorp.progressia.server.world.io.WorldContainer;
import ru.windcorp.progressia.server.world.tasks.WorldAccessor; import ru.windcorp.progressia.server.world.tasks.WorldAccessor;
import ru.windcorp.progressia.server.world.ticking.Change; import ru.windcorp.progressia.server.world.ticking.Change;
import ru.windcorp.progressia.server.world.ticking.Evaluation; import ru.windcorp.progressia.server.world.ticking.Evaluation;
@ -78,11 +77,16 @@ public class Server {
private final TickingSettings tickingSettings = new TickingSettings(); private final TickingSettings tickingSettings = new TickingSettings();
public Server(DefaultWorldData world, Function<Server, WorldGenerator> generatorCreator) { public Server(
DefaultWorldData world,
Function<Server, WorldGenerator> generatorCreator,
WorldContainer worldContainer
) {
this.world = new DefaultWorldLogic( this.world = new DefaultWorldLogic(
world, world,
this, this,
generatorCreator.apply(this), generatorCreator.apply(this),
worldContainer,
worldAccessor worldAccessor
); );
this.serverThread = new ServerThread(this); this.serverThread = new ServerThread(this);
@ -347,8 +351,8 @@ public class Server {
* reason * reason
*/ */
public void shutdown(String message) { public void shutdown(String message) {
LogManager.getLogger().warn("Server.shutdown() is not yet implemented");
serverThread.stop(); serverThread.stop();
getWorld().getContainer().close();
} }
private void scheduleWorldTicks(Server server) { private void scheduleWorldTicks(Server server) {

View File

@ -18,7 +18,14 @@
package ru.windcorp.progressia.server; package ru.windcorp.progressia.server;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.function.Function;
import ru.windcorp.progressia.common.world.DefaultWorldData; import ru.windcorp.progressia.common.world.DefaultWorldData;
import ru.windcorp.progressia.server.world.generation.WorldGenerator;
import ru.windcorp.progressia.server.world.io.WorldContainer;
import ru.windcorp.progressia.server.world.io.region.RegionFormat;
import ru.windcorp.progressia.test.gen.TestGenerationConfig; import ru.windcorp.progressia.test.gen.TestGenerationConfig;
public class ServerState { public class ServerState {
@ -33,10 +40,15 @@ public class ServerState {
ServerState.instance = instance; ServerState.instance = instance;
} }
public static void startServer() { public static void startServer() throws IOException {
Server server = new Server(new DefaultWorldData(), TestGenerationConfig.createGenerator());
Function<Server, WorldGenerator> generator = new TestGenerationConfig().getGenerator();
WorldContainer container = new RegionFormat("Test:Region").create(Paths.get("tmp_world"));
Server server = new Server(new DefaultWorldData(), generator, container);
setInstance(server); setInstance(server);
server.start(); server.start();
} }
private ServerState() { private ServerState() {

View File

@ -26,10 +26,13 @@ import org.apache.logging.log4j.LogManager;
import ru.windcorp.progressia.common.util.crash.CrashReports; import ru.windcorp.progressia.common.util.crash.CrashReports;
import ru.windcorp.progressia.server.world.ticking.TickerCoordinator; import ru.windcorp.progressia.server.world.ticking.TickerCoordinator;
@SuppressWarnings("unused")
public class ServerThread implements Runnable { public class ServerThread implements Runnable {
private static final ThreadLocal<Server> SERVER_THREADS_MAP = new ThreadLocal<>(); private static final ThreadLocal<Server> SERVER_THREADS_MAP = new ThreadLocal<>();
private static boolean isShuttingDown;
public static Server getCurrentServer() { public static Server getCurrentServer() {
return SERVER_THREADS_MAP.get(); return SERVER_THREADS_MAP.get();
} }
@ -63,6 +66,7 @@ public class ServerThread implements Runnable {
} }
public void start() { public void start() {
isShuttingDown = false;
ticker.start(); ticker.start();
executor.scheduleAtFixedRate(this, 0, 1000 / 20, TimeUnit.MILLISECONDS); executor.scheduleAtFixedRate(this, 0, 1000 / 20, TimeUnit.MILLISECONDS);
} }
@ -70,6 +74,11 @@ public class ServerThread implements Runnable {
@Override @Override
public void run() { public void run() {
try { try {
if (isShuttingDown) {
getTicker().stop();
executor.shutdown();
return;
}
server.tick(); server.tick();
ticker.runOneTick(); ticker.runOneTick();
} catch (Throwable e) { } catch (Throwable e) {
@ -78,13 +87,10 @@ public class ServerThread implements Runnable {
} }
public void stop() { public void stop() {
try {
executor.awaitTermination(10, TimeUnit.MINUTES);
} catch (InterruptedException e) {
LogManager.getLogger().warn("Received interrupt in ServerThread.stop(), aborting wait");
}
getTicker().stop(); isShuttingDown = true;
// getTicker().stop();
} }
public Server getServer() { public Server getServer() {

View File

@ -31,7 +31,6 @@ import ru.windcorp.progressia.common.comms.CommsChannel.State;
import ru.windcorp.progressia.common.comms.packets.Packet; import ru.windcorp.progressia.common.comms.packets.Packet;
import ru.windcorp.progressia.common.world.PacketSetGravityModel; import ru.windcorp.progressia.common.world.PacketSetGravityModel;
import ru.windcorp.progressia.common.world.PacketSetLocalPlayer; import ru.windcorp.progressia.common.world.PacketSetLocalPlayer;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.server.Player; import ru.windcorp.progressia.server.Player;
import ru.windcorp.progressia.server.Server; import ru.windcorp.progressia.server.Server;
@ -80,18 +79,20 @@ public class ClientManager {
setGravityModelPacket.set(getServer().getWorld().getData().getGravityModel()); setGravityModelPacket.set(getServer().getWorld().getData().getGravityModel());
client.sendPacket(setGravityModelPacket); client.sendPacket(setGravityModelPacket);
EntityData entity = getServer().getPlayerManager().conjurePlayerEntity(login); Player player = getServer().getPlayerManager().conjurePlayer(client, login);
Player player = new Player(entity, getServer(), client);
getServer().getPlayerManager().addPlayer(player); getServer().getPlayerManager().addPlayer(player);
PacketSetLocalPlayer packet = new PacketSetLocalPlayer(); PacketSetLocalPlayer packet = new PacketSetLocalPlayer();
packet.set(entity.getEntityId()); packet.set(player.getEntity().getEntityId());
client.sendPacket(packet); client.sendPacket(packet);
} }
public void disconnectClient(Client client) { public void disconnectClient(Client client) {
client.disconnect(); client.disconnect();
clientsById.remove(client.getId()); clientsById.remove(client.getId());
if (client instanceof ClientPlayer) {
getServer().getPlayerManager().removePlayer(((ClientPlayer) client).getPlayer());
}
} }
public void processPackets() { public void processPackets() {

View File

@ -28,7 +28,7 @@ import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.common.world.DefaultWorldData; import ru.windcorp.progressia.common.world.DefaultWorldData;
import ru.windcorp.progressia.server.Player; import ru.windcorp.progressia.server.Player;
import ru.windcorp.progressia.server.Server; import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.test.TestWorldDiskIO; import ru.windcorp.progressia.server.world.io.WorldContainer;
/** /**
* Chunk manager provides facilities to load, unload and generate chunks for a * Chunk manager provides facilities to load, unload and generate chunks for a
@ -56,6 +56,10 @@ public class ChunkManager {
return getLoadManager().getServer(); return getLoadManager().getServer();
} }
public WorldContainer getContainer() {
return getServer().getWorld().getContainer();
}
/** /**
* Describes the result of an attempt to load a chunk. * Describes the result of an attempt to load a chunk.
*/ */
@ -119,7 +123,7 @@ public class ChunkManager {
DefaultWorldData world = getServer().getWorld().getData(); DefaultWorldData world = getServer().getWorld().getData();
DefaultChunkData chunk = TestWorldDiskIO.tryToLoad(chunkPos, world, getServer()); DefaultChunkData chunk = getServer().getWorld().getContainer().load(chunkPos, world, getServer());
if (chunk != null) { if (chunk != null) {
world.addChunk(chunk); world.addChunk(chunk);
return LoadResult.LOADED_FROM_DISK; return LoadResult.LOADED_FROM_DISK;
@ -150,7 +154,7 @@ public class ChunkManager {
entitiesToRemove.forEach(world::removeEntity); entitiesToRemove.forEach(world::removeEntity);
world.removeChunk(chunk); world.removeChunk(chunk);
TestWorldDiskIO.saveChunk(chunk, getServer()); getContainer().save(chunk, getServer().getWorld().getData(), getServer());
return true; return true;
} }

View File

@ -33,6 +33,7 @@ import ru.windcorp.progressia.common.world.WorldDataListener;
import ru.windcorp.progressia.common.world.entity.EntityData; import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.server.Server; import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.server.world.generation.WorldGenerator; import ru.windcorp.progressia.server.world.generation.WorldGenerator;
import ru.windcorp.progressia.server.world.io.WorldContainer;
import ru.windcorp.progressia.server.world.tasks.TickEntitiesTask; import ru.windcorp.progressia.server.world.tasks.TickEntitiesTask;
import ru.windcorp.progressia.server.world.tasks.WorldAccessor; import ru.windcorp.progressia.server.world.tasks.WorldAccessor;
import ru.windcorp.progressia.server.world.ticking.Evaluation; import ru.windcorp.progressia.server.world.ticking.Evaluation;
@ -43,18 +44,21 @@ public class DefaultWorldLogic implements WorldLogic {
private final Server server; private final Server server;
private final WorldGenerator generator; private final WorldGenerator generator;
private final WorldContainer container;
private final Map<DefaultChunkData, DefaultChunkLogic> chunks = new HashMap<>(); private final Map<DefaultChunkData, DefaultChunkLogic> chunks = new HashMap<>();
private final Evaluation tickEntitiesTask = new TickEntitiesTask(); private final Evaluation tickEntitiesTask = new TickEntitiesTask();
public DefaultWorldLogic(DefaultWorldData data, Server server, WorldGenerator generator, WorldAccessor accessor) { public DefaultWorldLogic(DefaultWorldData data, Server server, WorldGenerator generator, WorldContainer container, WorldAccessor accessor) {
this.data = data; this.data = data;
this.server = server; this.server = server;
this.generator = generator; this.generator = generator;
data.setGravityModel(getGenerator().getGravityModel()); data.setGravityModel(getGenerator().getGravityModel());
this.container = container;
data.addListener(new WorldDataListener() { data.addListener(new WorldDataListener() {
@Override @Override
public void onChunkLoaded(DefaultWorldData world, DefaultChunkData chunk) { public void onChunkLoaded(DefaultWorldData world, DefaultChunkData chunk) {
@ -128,6 +132,10 @@ public class DefaultWorldLogic implements WorldLogic {
return generator; return generator;
} }
public WorldContainer getContainer() {
return container;
}
public DefaultChunkData generate(Vec3i chunkPos) { public DefaultChunkData generate(Vec3i chunkPos) {
DefaultChunkData chunk = getGenerator().generate(chunkPos); DefaultChunkData chunk = getGenerator().generate(chunkPos);

View File

@ -0,0 +1,43 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.server.world.io;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.DefaultWorldData;
import ru.windcorp.progressia.server.Player;
import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.server.comms.ClientPlayer;
import java.nio.file.Path;
public interface WorldContainer {
Path getPath();
DefaultChunkData load(Vec3i position, DefaultWorldData world, Server server);
void save(DefaultChunkData chunk, DefaultWorldData world, Server server);
Player loadPlayer(String login, ClientPlayer clientPlayer, Server server);
void savePlayer(Player player, Server server);
void close();
}

View File

@ -0,0 +1,33 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.server.world.io;
import java.io.IOException;
import java.nio.file.Path;
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
public abstract class WorldContainerFormat extends Namespaced {
public WorldContainerFormat(String id) {
super(id);
}
public abstract WorldContainer create(Path directory) throws IOException;
}

View File

@ -0,0 +1,166 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.server.world.io.region;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.state.IOContext;
import ru.windcorp.progressia.common.world.DecodingException;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.DefaultWorldData;
import ru.windcorp.progressia.common.world.generic.ChunkMap;
import ru.windcorp.progressia.common.world.generic.ChunkMaps;
import ru.windcorp.progressia.common.world.io.ChunkIO;
import ru.windcorp.progressia.server.Server;
public class Region {
private static final boolean RESET_CORRUPTED = true;
public int loadedChunks;
private AtomicBoolean isUsing = new AtomicBoolean(false);
private AtomicBoolean isClosed = new AtomicBoolean(false);
private final RegionFile file;
private final ChunkMap<Integer> offsets = ChunkMaps.newHashMap();
public Region(RandomAccessFile file, Vec3i regionCoords) throws IOException {
this.file = new RegionFile(file);
try {
this.file.confirmHeaderHealth(offsets, regionCoords);
} catch (IOException e) {
RegionWorldContainer.LOG.debug("Uh the file broke");
if (RESET_CORRUPTED) {
this.file.makeHeader(regionCoords);
}
}
}
public RegionFile getFile() {
return file;
}
public void close() throws IOException {
this.file.close();
isClosed.lazySet(true);
}
public int getOffset(Vec3i chunkLoc) {
return offsets.get(chunkLoc);
}
public boolean hasOffset(Vec3i pos) {
return offsets.containsKey(pos);
}
public void putOffset(Vec3i pos, int offset) {
offsets.put(pos, offset);
}
public AtomicBoolean isClosed() {
return isClosed;
}
public AtomicBoolean isUsing() {
return isUsing;
}
public void save(DefaultChunkData chunk, Server server) throws IOException {
isUsing.set(true);
Vec3i pos = RegionWorldContainer.getInRegionCoords(chunk.getPosition());
if (!hasOffset(pos)) {
putOffset(pos, file.allocateChunk(pos));
}
int dataOffset = getOffset(pos);
byte[] buffer = saveToBuffer(chunk, server);
file.writeBuffer(buffer, dataOffset, pos);
isUsing.set(false);
}
private byte[] saveToBuffer(DefaultChunkData chunk, Server server) throws IOException {
ByteArrayOutputStream arrayStream = new ByteArrayOutputStream();
try (
DataOutputStream dataStream = new DataOutputStream(
new DeflaterOutputStream(
new BufferedOutputStream(arrayStream)
)
)
) {
ChunkIO.save(chunk, dataStream, IOContext.SAVE);
RegionWorldContainer.writeGenerationHint(chunk, dataStream, server);
}
return arrayStream.toByteArray();
}
public DefaultChunkData load(Vec3i chunkPos, DefaultWorldData world, Server server)
throws IOException,
DecodingException {
isUsing.set(true);
int dataOffset = 0;
Vec3i pos = RegionWorldContainer.getInRegionCoords(chunkPos);
if (hasOffset(pos)) {
dataOffset = getOffset(pos);
} else {
return null;
}
byte[] buffer = file.readBuffer(dataOffset);
DefaultChunkData result = loadFromBuffer(buffer, chunkPos, world, server);
isUsing.set(false);
return result;
}
private DefaultChunkData loadFromBuffer(byte[] buffer, Vec3i chunkPos, DefaultWorldData world, Server server)
throws IOException,
DecodingException {
DataInputStream dataStream = new DataInputStream(
new InflaterInputStream(
new BufferedInputStream(
new ByteArrayInputStream(buffer)
)
)
);
DefaultChunkData result = ChunkIO.load(world, chunkPos, dataStream, IOContext.SAVE);
RegionWorldContainer.readGenerationHint(result, dataStream, server);
return result;
}
}

View File

@ -0,0 +1,268 @@
package ru.windcorp.progressia.server.world.io.region;
import static ru.windcorp.progressia.server.world.io.region.RegionWorldContainer.REGION_DIAMETER;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.util.HashSet;
import java.util.Set;
import org.apache.logging.log4j.LogManager;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.generic.ChunkMap;
/**
* Backend for the .progressia_region file.
* Use similarly to a file object
*/
public class RegionFile {
// 4 MiB
private static final int MAX_CHUNK_SIZE = 4 * 1024 * 1024;
private static final int SECTORS_BYTES = Short.BYTES;
private static final int SECTOR_SIZE = MAX_CHUNK_SIZE >> (SECTORS_BYTES * 8);
private static final int SECTOR_HEADER_SIZE = 1;
private static final int ID_HEADER_SIZE = 16;
private static final byte[] HEADER_ID = {'P','R','O','G'};
final byte endBytes[] = new byte[SECTOR_SIZE];
public static enum SectorType {
Ending(0), // Just an empty block
Data(1), // has a byte counting up in position 1, and then
PartitionLink(2),
BulkSaved(3); // TODO implement this
private final byte data;
SectorType(int i) {
this.data = (byte) i;
}
}
private static final int DEFINITION_SIZE = Integer.BYTES;
private static final int HEADER_SIZE = DEFINITION_SIZE * REGION_DIAMETER * REGION_DIAMETER * REGION_DIAMETER + ID_HEADER_SIZE;
private final RandomAccessFile file;
public RegionFile(RandomAccessFile inFile) {
file = inFile;
}
public void confirmHeaderHealth(ChunkMap<Integer> offsets, Vec3i regionCoords) throws IOException {
Set<Integer> used = new HashSet<Integer>();
int maxUsed = 0;
final int chunksPerRegion = REGION_DIAMETER * REGION_DIAMETER * REGION_DIAMETER;
if (file.length() < HEADER_SIZE) {
throw new IOException("File is too short to contain a header");
}
char prog;
for (int i=0;i<4;i++) {
prog = file.readChar();
if (prog != HEADER_ID[i])
{
throw new IOException("File is not a .progressia_chunk file");
}
}
int tempX = file.readInt();
int tempY = file.readInt();
int tempZ = file.readInt();
if (regionCoords.x != tempX || regionCoords.y != tempY || regionCoords.z != tempZ)
{
throw new IOException("Region file is in the wrong place/ has the wrong name.");
}
for (int i = 0; i < chunksPerRegion; i++) {
file.seek(i * DEFINITION_SIZE + ID_HEADER_SIZE);
int offset = file.readInt();
if (offset == 0) {
continue;
}
offset--;
Vec3i pos = new Vec3i();
pos.x = i / REGION_DIAMETER / REGION_DIAMETER;
pos.y = (i / REGION_DIAMETER) % REGION_DIAMETER;
pos.z = i % REGION_DIAMETER;
offsets.put(pos, offset);
boolean shouldEnd = false;
byte counter = 0;
while (!shouldEnd) {
if (offset > maxUsed) {
maxUsed = offset;
}
if (!used.add(offset)) {
throw new IOException("A sector is used twice");
}
file.seek(HEADER_SIZE + SECTOR_SIZE * offset);
byte type = file.readByte();
if (type == SectorType.Data.data) {
byte fileCounter = file.readByte();
if (fileCounter != counter) {
throw new IOException("An unexpected block was found");
}
counter++;
offset++;
} else if (type == SectorType.Ending.data) {
shouldEnd = true;
} else if (type == SectorType.PartitionLink.data) {
offset = file.readInt();
}
}
}
LogManager.getLogger("Region").debug("Efficiency of {}", (double) used.size() / maxUsed);
}
public void makeHeader(Vec3i regionCoords) throws IOException {
file.seek(0);
file.write(HEADER_ID);
file.writeInt(regionCoords.x);
file.writeInt(regionCoords.y);
file.writeInt(regionCoords.z);
for (int i = 0; i < HEADER_SIZE; i++) {
file.write(0);
}
}
public void writeBuffer(byte[] buffer, int dataOffset, Vec3i pos) throws IOException {
file.seek(HEADER_SIZE + SECTOR_SIZE * dataOffset);
int loc = 0;
byte tempBuffer[] = new byte[SECTOR_SIZE];
byte counter = 0;
boolean isDone = false;
while (!isDone) {
if (file.length() > HEADER_SIZE + SECTOR_SIZE * (dataOffset + 1)) {
file.seek(HEADER_SIZE + SECTOR_SIZE * (dataOffset + 1));
byte header = file.readByte();
if (header == SectorType.Data.data) {
byte fileCounter = file.readByte();
if (fileCounter != counter + 1) // This makes the actual
// partition place
{
int newOffset = allocateEmptySector();
file.seek(HEADER_SIZE + SECTOR_SIZE * dataOffset);
file.write(2);
file.writeInt(newOffset);
dataOffset = newOffset;
}
}
}
tempBuffer[0] = 1;
tempBuffer[1] = counter;
counter++;
for (int i = 0; i < (SECTOR_SIZE - SECTOR_HEADER_SIZE - 1); i++) {
if (loc * (SECTOR_SIZE - SECTOR_HEADER_SIZE - 1) + i < buffer.length) {
tempBuffer[i + SECTOR_HEADER_SIZE + 1] = buffer[loc * (SECTOR_SIZE - SECTOR_HEADER_SIZE - 1) + i];
} else {
isDone = true;
break;
}
}
loc++;
if (file.getFilePointer() < 256)
LogManager.getLogger("Region")
.debug("at {}, ({},{},{}), {}", file.getFilePointer(), pos.x, pos.y, pos.z, dataOffset);
file.seek(HEADER_SIZE + SECTOR_SIZE * dataOffset);
dataOffset++;
file.write(tempBuffer);
}
file.write(endBytes);
}
public int allocateChunk(Vec3i pos) throws IOException {
int definitionOffset = ID_HEADER_SIZE + DEFINITION_SIZE * (pos.z + REGION_DIAMETER * (pos.y + REGION_DIAMETER * pos.x));
int outputLen = (int) file.length();
int dataOffset = (int) Math.ceil((double) (outputLen - HEADER_SIZE) / SECTOR_SIZE);
file.seek(definitionOffset);
file.writeInt(dataOffset + 1);
file.setLength(HEADER_SIZE + dataOffset * SECTOR_SIZE);
return dataOffset;
}
private int allocateEmptySector() throws IOException {
int outputLen = (int) file.length();
int dataOffset = (int) Math.ceil((double) (outputLen - HEADER_SIZE) / SECTOR_SIZE);
file.setLength(HEADER_SIZE + dataOffset * SECTOR_SIZE);
return dataOffset;
}
public byte[] readBuffer(int dataOffset) throws IOException {
file.seek(HEADER_SIZE + SECTOR_SIZE * dataOffset);
int bufferPos = 0;
byte buffer[] = new byte[SECTOR_SIZE * 16];
byte tempBuffer[] = new byte[SECTOR_SIZE];
boolean reachedEnd = false;
byte counter = 0;
while (!reachedEnd) {
int bytesRead = file.read(tempBuffer, 0, SECTOR_SIZE);
if (bytesRead < 0) {
reachedEnd = true;
continue;
}
if (tempBuffer[0] == SectorType.Data.data) {
if (tempBuffer[1] != counter) {
throw new IOException(
"Sectors were read out of order\nExpected chunk number " + Byte.toString(counter)
+ " but encountered number " + Byte.toString(tempBuffer[1])
);
}
counter++;
if (buffer.length - bufferPos < SECTOR_SIZE - SECTOR_HEADER_SIZE - 1) {
byte newBuffer[] = new byte[buffer.length + SECTOR_SIZE * 16];
for (int i = 0; i < buffer.length; i++) // TODO dedicated
// copy, java-y at
// least
{
newBuffer[i] = buffer[i];
}
buffer = newBuffer;
}
for (int i = 0; i < SECTOR_SIZE - SECTOR_HEADER_SIZE - 1; i++) {
buffer[bufferPos + i] = tempBuffer[i + 2];
}
bufferPos += SECTOR_SIZE - SECTOR_HEADER_SIZE - 1;
} else if (tempBuffer[0] == SectorType.Ending.data) {
reachedEnd = true;
} else if (tempBuffer[0] == SectorType.PartitionLink.data) {
ByteBuffer intBuffer = ByteBuffer.wrap(tempBuffer);
int newOffset = intBuffer.getInt(1);
file.seek(HEADER_SIZE + SECTOR_SIZE * newOffset);
} else {
throw new IOException("Invalid sector ID.");
}
}
return buffer;
}
public void close() throws IOException {
file.close();
}
}

View File

@ -0,0 +1,37 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.server.world.io.region;
import java.io.IOException;
import java.nio.file.Path;
import ru.windcorp.progressia.server.world.io.WorldContainer;
import ru.windcorp.progressia.server.world.io.WorldContainerFormat;
public class RegionFormat extends WorldContainerFormat {
public RegionFormat(String id) {
super(id);
}
@Override
public WorldContainer create(Path directory) throws IOException {
return new RegionWorldContainer(directory);
}
}

View File

@ -0,0 +1,266 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.server.world.io.region;
import java.util.concurrent.atomic.AtomicBoolean;
import glm.vec._3.i.Vec3i;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import ru.windcorp.progressia.common.state.IOContext;
import ru.windcorp.progressia.common.util.crash.CrashReports;
import ru.windcorp.progressia.common.world.Coordinates;
import ru.windcorp.progressia.common.world.DecodingException;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.DefaultWorldData;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.common.world.entity.EntityDataRegistry;
import ru.windcorp.progressia.common.world.generic.ChunkMap;
import ru.windcorp.progressia.common.world.generic.ChunkMaps;
import ru.windcorp.progressia.server.Player;
import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.server.comms.ClientPlayer;
import ru.windcorp.progressia.server.world.io.WorldContainer;
import ru.windcorp.progressia.test.TestContent;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
public class RegionWorldContainer implements WorldContainer {
private static final boolean ENABLE = true;
private static final String REGION_FOLDER_NAME = "regions";
private static final String PLAYERS_FOLDER_NAME = "players";
private static final String REGION_NAME_FORMAT = REGION_FOLDER_NAME + "/" + "region_%d_%d_%d.progressia_region";
private static final String PLAYER_NAME_FORMAT = PLAYERS_FOLDER_NAME + "/" + "%s.progressia_player";
private static final int BITS_IN_CHUNK_COORDS = 4;
public static final int REGION_DIAMETER = 1 << BITS_IN_CHUNK_COORDS;
public static Vec3i getRegionCoords(Vec3i chunkCoords) {
return Coordinates.convertGlobalToCell(BITS_IN_CHUNK_COORDS, chunkCoords, null);
}
public static Vec3i getInRegionCoords(Vec3i chunkCoords) {
return Coordinates.convertGlobalToInCell(BITS_IN_CHUNK_COORDS, chunkCoords, null);
}
static final Logger LOG = LogManager.getLogger("TestWorldDiskIO");
private final Path path;
private final ChunkMap<Region> regions = ChunkMaps.newHashMap();
public RegionWorldContainer(Path path) throws IOException {
this.path = path;
Files.createDirectories(getPath());
Files.createDirectories(getPath().resolve(REGION_FOLDER_NAME));
Files.createDirectories(getPath().resolve(PLAYERS_FOLDER_NAME));
}
@Override
public DefaultChunkData load(Vec3i chunkPos, DefaultWorldData world, Server server) {
if (!ENABLE) {
return null;
}
try {
Region region = getRegion(chunkPos, false);
if (region == null) {
debug("Could not load chunk {} {} {}: region did not load", chunkPos);
return null;
}
DefaultChunkData result = region.load(chunkPos, world, server);
return result;
} catch (IOException | DecodingException e) {
warn("Failed to load chunk {} {} {}", chunkPos);
e.printStackTrace();
return null;
}
}
@Override
public void save(DefaultChunkData chunk, DefaultWorldData world, Server server) {
if (!ENABLE) {
return;
}
try {
debug("Saving chunk {} {} {}", chunk.getPosition());
Region region = getRegion(chunk.getPosition(), true);
region.save(chunk, server);
} catch (IOException e) {
warn("Failed to save chunk {} {} {}", chunk.getPosition());
e.printStackTrace();
}
}
@Override
public Player loadPlayer(String login, ClientPlayer clientPlayer, Server server) {
Path path = getPlayerPath(login);
if (!Files.exists(path)) {
LOG.debug("Could not load player {} because file {} does not exist", login, path);
return null;
}
EntityData player = EntityDataRegistry.getInstance().create("Test:Player");
try (
DataInputStream dataInputStream = new DataInputStream(
new BufferedInputStream(
Files.newInputStream(
getPlayerPath(login)
)
)
)
) {
player.read(dataInputStream, IOContext.SAVE);
player.setEntityId(TestContent.PLAYER_ENTITY_ID);
return new Player(player, server, clientPlayer);
} catch (IOException ioException) {
throw CrashReports.report(ioException, "Could not load player data: " + login);
}
}
@Override
public void savePlayer(Player player, Server server) {
Path path = getPlayerPath(player.getLogin());
try (
DataOutputStream dataOutputStream = new DataOutputStream(
new BufferedOutputStream(
Files.newOutputStream(path)
)
)
) {
player.getEntity().
write(dataOutputStream, IOContext.SAVE);
} catch (IOException ioException) {
throw CrashReports.report(ioException, "Could not save player %s data in file ", player.getLogin(), path);
}
}
private Region getRegion(Vec3i position, boolean createIfMissing) throws IOException {
Vec3i regionCoords = getRegionCoords(position);
Region region = regions.get(regionCoords);
if (region == null) {
debug("Region {} {} {} is not loaded, loading", regionCoords);
Path path = getRegionPath(regionCoords);
if (!createIfMissing && !Files.exists(path)) {
debug("Region {} {} {} does not exist on disk, aborting load", regionCoords);
return null;
}
region = openRegion(path, regionCoords);
debug("Region {} {} {} loaded", regionCoords);
}
return region;
}
private Region openRegion(Path path, Vec3i regionCoords) throws IOException {
RandomAccessFile raf = new RandomAccessFile(path.toFile(), "rw");
Region region = new Region(raf, regionCoords);
regions.put(regionCoords, region);
return region;
}
static void writeGenerationHint(DefaultChunkData chunk, DataOutputStream output, Server server)
throws IOException {
server.getWorld().getGenerator().writeGenerationHint(output, chunk.getGenerationHint());
}
static void readGenerationHint(DefaultChunkData chunk, DataInputStream input, Server server)
throws IOException,
DecodingException {
chunk.setGenerationHint(server.getWorld().getGenerator().readGenerationHint(input));
}
@Override
public Path getPath() {
return path;
}
private Path getRegionPath(Vec3i regionPos) {
return getPath().resolve(
String.format(
REGION_NAME_FORMAT,
regionPos.x,
regionPos.y,
regionPos.z
)
);
}
private Path getPlayerPath(String login) {
return getPath().resolve(
String.format(
PLAYER_NAME_FORMAT,
login
)
);
}
@Override
public void close() {
try {
ChunkMap<AtomicBoolean> isCloseds = ChunkMaps.newHashMap();
ChunkMap<AtomicBoolean> isUsings = ChunkMaps.newHashMap();
for (Vec3i region : regions.keys()) {
isCloseds.put(region, regions.get(region).isClosed());
isUsings.put(region, regions.get(region).isUsing());
}
boolean stillOpen = true;
while (stillOpen) {
stillOpen = false;
for (Vec3i region : regions.keys()) {
if (!isCloseds.get(region).get() && !isUsings.get(region).get()) {
regions.get(region).close();
} else if (isUsings.get(region).get()) {
stillOpen = false;
}
}
}
} catch (IOException e) {
throw CrashReports.report(e, "Could not close region files");
}
}
private static void debug(String message, Vec3i vector) {
if (LOG.isDebugEnabled()) {
LOG.debug(message, vector.x, vector.y, vector.z);
}
}
private static void warn(String message, Vec3i vector) {
LOG.warn(message, vector.x, vector.y, vector.z);
}
}

View File

@ -17,7 +17,11 @@
*/ */
package ru.windcorp.progressia.test; package ru.windcorp.progressia.test;
import java.util.Collection;
import ru.windcorp.progressia.client.ClientState;
import ru.windcorp.progressia.client.graphics.Colors; import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.GUI;
import ru.windcorp.progressia.client.graphics.font.Font; import ru.windcorp.progressia.client.graphics.font.Font;
import ru.windcorp.progressia.client.graphics.gui.Button; import ru.windcorp.progressia.client.graphics.gui.Button;
import ru.windcorp.progressia.client.graphics.gui.Checkbox; import ru.windcorp.progressia.client.graphics.gui.Checkbox;
@ -25,9 +29,15 @@ import ru.windcorp.progressia.client.graphics.gui.Label;
import ru.windcorp.progressia.client.graphics.gui.RadioButton; import ru.windcorp.progressia.client.graphics.gui.RadioButton;
import ru.windcorp.progressia.client.graphics.gui.RadioButtonGroup; import ru.windcorp.progressia.client.graphics.gui.RadioButtonGroup;
import ru.windcorp.progressia.client.graphics.gui.menu.MenuLayer; import ru.windcorp.progressia.client.graphics.gui.menu.MenuLayer;
import ru.windcorp.progressia.client.localization.MutableStringLocalized;
import ru.windcorp.progressia.server.Player;
import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.server.ServerState;
public class LayerButtonTest extends MenuLayer { public class LayerButtonTest extends MenuLayer {
boolean alive = true;
public LayerButtonTest() { public LayerButtonTest() {
super("ButtonTest"); super("ButtonTest");
@ -60,8 +70,28 @@ public class LayerButtonTest extends MenuLayer {
getCloseAction().run(); getCloseAction().run();
})); }));
getContent().addChild(new Button("Quit", "Quit").addAction(b -> { getContent().addChild(new Button("Menu", "Back To Menu").addAction(b -> {
System.exit(0); getCloseAction().run();
Collection<Player> players = ServerState.getInstance().getPlayerManager().getPlayers();
players.clear();
ClientState.disconnectFromLocalServer();
GUI.addTopLayer(new LayerTestText("Text", new MutableStringLocalized("LayerText.Save"), layer -> {
Server server = ServerState.getInstance();
if (server != null && server.getWorld().getChunks().isEmpty()) {
GUI.removeLayer(layer);
// TODO Refactor, this shouldn't be here
GUI.addTopLayer(new LayerTitle("Title"));
ServerState.getInstance().shutdown("Safe Exit");
ServerState.setInstance(null);
TestPlayerControls.resetInstance();
}
}));
ClientState.setInstance(null);
})); }));
getContent().takeFocus(); getContent().takeFocus();

View File

@ -0,0 +1,29 @@
package ru.windcorp.progressia.test;
import java.util.function.Consumer;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.font.Font;
import ru.windcorp.progressia.client.graphics.gui.GUILayer;
import ru.windcorp.progressia.client.graphics.gui.Label;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
import ru.windcorp.progressia.client.localization.MutableString;
public class LayerTestText extends GUILayer {
private final Consumer<LayerTestText> remover;
public LayerTestText(String name, MutableString value, Consumer<LayerTestText> remover) {
super(name, new LayoutAlign(15));
this.remover = remover;
Font titleFont = new Font().deriveBold().withColor(Colors.BLACK).withAlign(0.5f);
getRoot().addChild(new Label(name + ".Text", titleFont, value));
}
@Override
protected void doRender() {
super.doRender();
remover.accept(this);
}
}

View File

@ -0,0 +1,95 @@
package ru.windcorp.progressia.test;
import ru.windcorp.progressia.client.ClientState;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.GUI;
import ru.windcorp.progressia.client.graphics.font.Font;
import ru.windcorp.progressia.client.graphics.gui.BasicButton;
import ru.windcorp.progressia.client.graphics.gui.Button;
import ru.windcorp.progressia.client.graphics.gui.GUILayer;
import ru.windcorp.progressia.client.graphics.gui.Group;
import ru.windcorp.progressia.client.graphics.gui.Label;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical;
import ru.windcorp.progressia.client.localization.MutableString;
import ru.windcorp.progressia.client.localization.MutableStringLocalized;
import ru.windcorp.progressia.common.util.crash.CrashReports;
import ru.windcorp.progressia.server.ServerState;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
public class LayerTitle extends GUILayer {
private final BasicButton resetButton;
public LayerTitle(String name) {
super(name, new LayoutAlign(0.5f, 0.7f, 15));
Group content = new Group("Layer" + name + ".Group", new LayoutVertical(15));
MutableString title = new MutableStringLocalized("Layer" + name + ".Title");
Font titleFont = new Font().deriveBold().withColor(Colors.BLACK).withAlign(0.5f);
content.addChild(new Label(name + ".Title", titleFont, title));
Font buttonFont = titleFont.deriveNotBold();
MutableString playText = new MutableStringLocalized("Layer" + name + ".Play");
content.addChild(new Button(name + ".Play", new Label(name + ".Play", buttonFont, playText)).addAction(this::startGame));
MutableString resetText = new MutableStringLocalized("Layer" + name + ".Reset");
this.resetButton = new Button(name + ".Reset", new Label(name + ".Reset", buttonFont, resetText)).addAction(this::resetWorld);
content.addChild(resetButton);
updateResetButton();
MutableString quitText = new MutableStringLocalized("Layer" + name + ".Quit");
content.addChild(new Button(name + "Quit", new Label(name + ".Quit", buttonFont, quitText)).addAction(b -> {
System.exit(0);
}));
getRoot().addChild(content);
}
private void updateResetButton() {
resetButton.setEnabled(Files.exists(Paths.get("tmp_world")));
}
private void startGame(BasicButton basicButton) {
GUI.removeLayer(this);
try {
ServerState.startServer();
ClientState.connectToLocalServer();
} catch (IOException e) {
throw CrashReports.report(e, "Problem with loading server");
}
}
private void resetWorld(BasicButton basicButton) {
Path rootPath = Paths.get("tmp_world");
try {
Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
} catch (IOException e) {
throw CrashReports.report(e, "Could not reset world");
}
updateResetButton();
}
}

View File

@ -46,10 +46,10 @@ import ru.windcorp.progressia.server.ServerState;
public class TestPlayerControls { public class TestPlayerControls {
private static final TestPlayerControls INSTANCE = new TestPlayerControls(); private static TestPlayerControls instance = new TestPlayerControls();
public static TestPlayerControls getInstance() { public static TestPlayerControls getInstance() {
return INSTANCE; return instance;
} }
private static final double MODE_SWITCH_MAX_DELAY = 300 * Units.MILLISECONDS; private static final double MODE_SWITCH_MAX_DELAY = 300 * Units.MILLISECONDS;
@ -91,6 +91,10 @@ public class TestPlayerControls {
private LayerTestGUI debugLayer = null; private LayerTestGUI debugLayer = null;
private Runnable updateCallback = null; private Runnable updateCallback = null;
public static void resetInstance() {
instance = new TestPlayerControls();
}
public void applyPlayerControls() { public void applyPlayerControls() {
if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) { if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
return; return;

View File

@ -1,158 +0,0 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.test;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.state.IOContext;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.DecodingException;
import ru.windcorp.progressia.common.world.DefaultWorldData;
import ru.windcorp.progressia.common.world.io.ChunkIO;
import ru.windcorp.progressia.server.Server;
public class TestWorldDiskIO {
private static final Path SAVE_DIR = Paths.get("tmp_world");
private static final Logger LOG = LogManager.getLogger("TestWorldDiskIO");
private static final boolean ENABLE = false;
public static void saveChunk(DefaultChunkData chunk, Server server) {
if (!ENABLE)
return;
try {
LOG.debug(
"Saving {} {} {}",
chunk.getPosition().x,
chunk.getPosition().y,
chunk.getPosition().z
);
Files.createDirectories(SAVE_DIR);
Path path = SAVE_DIR.resolve(
String.format(
"chunk_%+d_%+d_%+d.progressia_chunk",
chunk.getPosition().x,
chunk.getPosition().y,
chunk.getPosition().z
)
);
try (
DataOutputStream output = new DataOutputStream(
new DeflaterOutputStream(new BufferedOutputStream(Files.newOutputStream(path)))
)
) {
ChunkIO.save(chunk, output, IOContext.SAVE);
writeGenerationHint(chunk, output, server);
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static void writeGenerationHint(DefaultChunkData chunk, DataOutputStream output, Server server)
throws IOException {
server.getWorld().getGenerator().writeGenerationHint(output, chunk.getGenerationHint());
}
public static DefaultChunkData tryToLoad(Vec3i chunkPos, DefaultWorldData world, Server server) {
if (!ENABLE)
return null;
Path path = SAVE_DIR.resolve(
String.format(
"chunk_%+d_%+d_%+d.progressia_chunk",
chunkPos.x,
chunkPos.y,
chunkPos.z
)
);
if (!Files.exists(path)) {
LOG.debug(
"Not found {} {} {}",
chunkPos.x,
chunkPos.y,
chunkPos.z
);
return null;
}
try {
DefaultChunkData result = load(path, chunkPos, world, server);
LOG.debug(
"Loaded {} {} {}",
chunkPos.x,
chunkPos.y,
chunkPos.z
);
return result;
} catch (Exception e) {
e.printStackTrace();
LOG.debug(
"Could not load {} {} {}",
chunkPos.x,
chunkPos.y,
chunkPos.z
);
return null;
}
}
private static DefaultChunkData load(Path path, Vec3i chunkPos, DefaultWorldData world, Server server)
throws IOException,
DecodingException {
try (
DataInputStream input = new DataInputStream(
new InflaterInputStream(new BufferedInputStream(Files.newInputStream(path)))
)
) {
DefaultChunkData chunk = ChunkIO.load(world, chunkPos, input, IOContext.SAVE);
readGenerationHint(chunk, input, server);
return chunk;
}
}
private static void readGenerationHint(DefaultChunkData chunk, DataInputStream input, Server server)
throws IOException,
DecodingException {
chunk.setGenerationHint(server.getWorld().getGenerator().readGenerationHint(input));
}
}

View File

@ -46,9 +46,18 @@ public class TestGenerationConfig {
private static final float CURVATURE = Units.get("100 m"); private static final float CURVATURE = Units.get("100 m");
private static final float INNER_RADIUS = Units.get("200 m"); private static final float INNER_RADIUS = Units.get("200 m");
private static final Fields FIELDS = new Fields(SEED); private final Fields fields = new Fields(SEED);
private final Function<Server, WorldGenerator> generator;
public static Function<Server, WorldGenerator> createGenerator() { public TestGenerationConfig() {
this.generator = createGenerator();
}
public Function<Server, WorldGenerator> getGenerator() {
return generator;
}
private Function<Server, WorldGenerator> createGenerator() {
Planet planet = new Planet( Planet planet = new Planet(
((int) PLANET_RADIUS) / Coordinates.CHUNK_SIZE, ((int) PLANET_RADIUS) / Coordinates.CHUNK_SIZE,
@ -57,7 +66,7 @@ public class TestGenerationConfig {
INNER_RADIUS INNER_RADIUS
); );
TestHeightMap heightMap = new TestHeightMap(planet, planet.getRadius() / 4, FIELDS); TestHeightMap heightMap = new TestHeightMap(planet, planet.getRadius() / 4, fields);
LayeredTerrain terrain = new LayeredTerrain(); LayeredTerrain terrain = new LayeredTerrain();
registerTerrainLayers(terrain); registerTerrainLayers(terrain);
@ -69,12 +78,12 @@ public class TestGenerationConfig {
} }
private static void registerTerrainLayers(LayeredTerrain terrain) { private void registerTerrainLayers(LayeredTerrain terrain) {
SurfaceFloatField cliffs = FIELDS.get("Test:Cliff"); SurfaceFloatField cliffs = fields.get("Test:Cliff");
SurfaceFloatField beaches = FIELDS.register( SurfaceFloatField beaches = fields.register(
"Test:Beach", "Test:Beach",
f -> multiply( f -> multiply(
anti(FIELDS.get("Test:Cliff", f)) anti(fields.get("Test:Cliff", f))
) )
); );
RockStrata rockStrata = createStrata(); RockStrata rockStrata = createStrata();
@ -88,40 +97,40 @@ public class TestGenerationConfig {
terrain.addLayer(new BeachLayer("Test:Beaches", beaches, rockStrata)); terrain.addLayer(new BeachLayer("Test:Beaches", beaches, rockStrata));
} }
private static RockStrata createStrata() { private RockStrata createStrata() {
WorleyProceduralNoise.Builder<Rock> builder = WorleyProceduralNoise.builder(); WorleyProceduralNoise.Builder<Rock> builder = WorleyProceduralNoise.builder();
TestContent.ROCKS.getRocks().forEach(rock -> builder.add(rock, 1)); TestContent.ROCKS.getRocks().forEach(rock -> builder.add(rock, 1));
SurfaceFloatField rockDepthOffsets = FIELDS.register( SurfaceFloatField rockDepthOffsets = fields.register(
"Test:RockDepthOffsets", "Test:RockDepthOffsets",
() -> tweak(FIELDS.primitive(), 40, 5) () -> tweak(fields.primitive(), 40, 5)
); );
return new RockStrata(builder.build(SEED), rockDepthOffsets); return new RockStrata(builder.build(SEED), rockDepthOffsets);
} }
private static void registerFeatures(List<SurfaceFeature> features) { private void registerFeatures(List<SurfaceFeature> features) {
SurfaceFloatField forestiness = FIELDS.register( SurfaceFloatField forestiness = fields.register(
"Test:Forest", "Test:Forest",
() -> squash(scale(FIELDS.primitive(), 200), 5) () -> squash(scale(fields.primitive(), 200), 5)
); );
SurfaceFloatField grassiness = FIELDS.register( SurfaceFloatField grassiness = fields.register(
"Test:Grass", "Test:Grass",
f -> multiply( f -> multiply(
tweak(octaves(FIELDS.primitive(), 2, 2), 40, 0.5, 1.2), tweak(octaves(fields.primitive(), 2, 2), 40, 0.5, 1.2),
squash(tweak(FIELDS.get("Test:Forest", f), 1, -1, 1), 10), squash(tweak(fields.get("Test:Forest", f), 1, -1, 1), 10),
anti(squash(FIELDS.get("Test:Cliff", f), 10)) anti(squash(fields.get("Test:Cliff", f), 10))
) )
); );
Function<String, SurfaceFloatField> floweriness = flowerName -> FIELDS.register( Function<String, SurfaceFloatField> floweriness = flowerName -> fields.register(
"Test:Flower" + flowerName, "Test:Flower" + flowerName,
f -> multiply( f -> multiply(
selectPositive(squash(scale(octaves(FIELDS.primitive(), 2, 3), 100), 2), 1, 0.5), selectPositive(squash(scale(octaves(fields.primitive(), 2, 3), 100), 2), 1, 0.5),
tweak(FIELDS.get("Test:Forest", f), 1, -1, 1.1), tweak(fields.get("Test:Forest", f), 1, -1, 1.1),
anti(squash(FIELDS.get("Test:Cliff", f), 10)) anti(squash(fields.get("Test:Cliff", f), 10))
) )
); );

View File

@ -22,3 +22,13 @@ LayerTestGUI.IsFullscreen = Fullscreen: %5s (F11)
LayerTestGUI.IsVSync = VSync: %5s (F12) LayerTestGUI.IsVSync = VSync: %5s (F12)
LayerButtonTest.Title = Button Test LayerButtonTest.Title = Button Test
LayerButtonTest.Return = Back To Menu
LayerTitle.Title = Progressia
LayerTitle.Play = Play World
LayerTitle.Reset = Reset World
LayerTitle.Options = Options
LayerTitle.Quit = Quit
LayerText.Load = Loading...
LayerText.Save = Saving...

View File

@ -21,4 +21,14 @@ LayerTestGUI.PlacementModeHint = (Блок %s плитки: Ctrl + прокру
LayerTestGUI.IsFullscreen = Полный экран: %5s (F11) LayerTestGUI.IsFullscreen = Полный экран: %5s (F11)
LayerTestGUI.IsVSync = Верт. синхр.: %5s (F12) LayerTestGUI.IsVSync = Верт. синхр.: %5s (F12)
LayerButtonTest.Title = Тест Кнопок LayerButtonTest.Title = Тест кнопок
LayerButtonTest.Return = Главное меню
LayerTitle.Title = Прогрессия
LayerTitle.Play = Играть
LayerTitle.Reset = Сбросить мир
LayerTitle.Options = Настройки
LayerTitle.Quit = Выход
LayerText.Load = Загрузка...
LayerText.Save = Сохранение...

View File

@ -27,6 +27,10 @@
<Logger name="Ticker 0" level="DEBUG" /> <Logger name="Ticker 0" level="DEBUG" />
--> -->
<!-- Uncomment to enable Region file logger debugging
<Logger name="TestWorldDiskIO" level="DEBUG" />
-->
<Root level="info"> <Root level="info">
<AppenderRef ref="FileLog" /> <AppenderRef ref="FileLog" />
<AppenderRef ref="Console" /> <AppenderRef ref="Console" />