Added better error handling, and prepared for arena ship management.
This commit is contained in:
parent
102f1c7b35
commit
92e60135fe
|
@ -29,7 +29,9 @@ import java.util.concurrent.Executors;
|
|||
@Service
|
||||
@Slf4j
|
||||
public class SocketGateway implements Runnable {
|
||||
public static final int INITIALIZATION_TIMEOUT = 1000;
|
||||
/**
|
||||
* The serializer that's used for communication between arenas and clients.
|
||||
*/
|
||||
public static final Serializer SERIALIZER = new Serializer();
|
||||
static {
|
||||
SERIALIZER.registerType(1, ClientConnectRequest.class);
|
||||
|
@ -50,6 +52,8 @@ public class SocketGateway implements Runnable {
|
|||
private short tcpPort;
|
||||
@Value("${starship-arena.gateway.udp-port}") @Getter
|
||||
private short udpPort;
|
||||
@Value("${starship-arena.gateway.init-timeout-ms}")
|
||||
private int initTimeout;
|
||||
|
||||
public SocketGateway(ArenaStore arenaStore) throws IOException {
|
||||
this.serverSocket = new ServerSocket();
|
||||
|
@ -104,7 +108,7 @@ public class SocketGateway implements Runnable {
|
|||
*/
|
||||
private void processIncomingConnection(Socket clientSocket) {
|
||||
try {
|
||||
clientSocket.setSoTimeout(INITIALIZATION_TIMEOUT); // Set limited timeout so new connections don't waste resources.
|
||||
clientSocket.setSoTimeout(initTimeout); // Set limited timeout so new connections don't waste resources.
|
||||
Message msg = SERIALIZER.readMessage(clientSocket.getInputStream());
|
||||
if (msg instanceof ClientConnectRequest cm) {
|
||||
UUID arenaId = cm.arenaId();
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package nl.andrewl.starship_arena.server.api;
|
||||
|
||||
import nl.andrewl.starship_arena.server.api.dto.ErrorResponse;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
@RestControllerAdvice
|
||||
public class ApiExceptionHandler {
|
||||
@ExceptionHandler(ResponseStatusException.class)
|
||||
public ResponseEntity<ErrorResponse> handleRSE(ResponseStatusException e) {
|
||||
return new ResponseEntity<>(new ErrorResponse(e.getRawStatusCode(), e.getReason()), e.getStatus());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package nl.andrewl.starship_arena.server.api.dto;
|
||||
|
||||
public record ErrorResponse(
|
||||
int status,
|
||||
String message
|
||||
) {}
|
|
@ -6,6 +6,7 @@ import nl.andrewl.starship_arena.server.api.dto.ArenaResponse;
|
|||
import nl.andrewl.starship_arena.server.control.ArenaUpdater;
|
||||
import nl.andrewl.starship_arena.server.model.Arena;
|
||||
import nl.andrewl.starship_arena.server.model.ArenaStage;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
|
@ -25,10 +26,14 @@ import java.util.concurrent.TimeUnit;
|
|||
@EnableScheduling
|
||||
@Slf4j
|
||||
public class ArenaStore {
|
||||
@Value("${starship-arena.max-arenas : 100}")
|
||||
private int maxArenas;
|
||||
|
||||
private final Map<UUID, Arena> arenas = new ConcurrentHashMap<>();
|
||||
|
||||
public List<ArenaResponse> getArenas() {
|
||||
return arenas.values().stream()
|
||||
.filter(Arena::isActive)
|
||||
.sorted(Comparator.comparing(Arena::getCreatedAt))
|
||||
.map(ArenaResponse::new)
|
||||
.toList();
|
||||
|
@ -38,25 +43,30 @@ public class ArenaStore {
|
|||
return Optional.ofNullable(arenas.get(id));
|
||||
}
|
||||
|
||||
private Arena getOrThrow(String id) {
|
||||
try {
|
||||
UUID uuid = UUID.fromString(id);
|
||||
Arena arena = arenas.get(uuid);
|
||||
if (arena == null) throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Arena with id \"" + id + "\" not found.");
|
||||
return arena;
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Malformed id. Should be a UUID.");
|
||||
}
|
||||
}
|
||||
|
||||
public ArenaResponse registerArena(ArenaCreationPayload payload) {
|
||||
if (arenas.size() >= maxArenas) throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Too many arenas.");
|
||||
Arena arena = new Arena(payload.name());
|
||||
this.arenas.put(arena.getId(), arena);
|
||||
return new ArenaResponse(arena);
|
||||
}
|
||||
|
||||
public ArenaResponse getArena(String arenaId) {
|
||||
try {
|
||||
Arena arena = arenas.get(UUID.fromString(arenaId));
|
||||
if (arena == null) throw new ResponseStatusException(HttpStatus.NOT_FOUND);
|
||||
return new ArenaResponse(arena);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid arena id.");
|
||||
}
|
||||
return new ArenaResponse(getOrThrow(arenaId));
|
||||
}
|
||||
|
||||
public void startArena(String arenaId) {
|
||||
Arena arena = arenas.get(UUID.fromString(arenaId));
|
||||
if (arena == null) throw new ResponseStatusException(HttpStatus.NOT_FOUND);
|
||||
Arena arena = getOrThrow(arenaId);
|
||||
if (arena.getCurrentStage() != ArenaStage.PRE_STAGING) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Arena is already started.");
|
||||
}
|
||||
|
@ -67,10 +77,12 @@ public class ArenaStore {
|
|||
public void cleanArenas() {
|
||||
Set<UUID> removalSet = new HashSet<>();
|
||||
final Instant cutoff = Instant.now().minus(5, ChronoUnit.MINUTES);
|
||||
final Instant hardCutoff = Instant.now().minus(1, ChronoUnit.HOURS);
|
||||
for (var arena : arenas.values()) {
|
||||
if (
|
||||
(arena.getCurrentStage() == ArenaStage.CLOSED) ||
|
||||
(arena.getCurrentStage() == ArenaStage.PRE_STAGING && arena.getCreatedAt().isBefore(cutoff))
|
||||
(arena.getCurrentStage() == ArenaStage.PRE_STAGING && arena.getCreatedAt().isBefore(cutoff)) ||
|
||||
(arena.getCreatedAt().isBefore(hardCutoff))
|
||||
) {
|
||||
removalSet.add(arena.getId());
|
||||
}
|
||||
|
|
|
@ -73,6 +73,10 @@ public class Arena {
|
|||
this.closesAt = battleEndsAt.plus(1, ChronoUnit.MINUTES);
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return currentStage != ArenaStage.PRE_STAGING && currentStage != ArenaStage.CLOSED;
|
||||
}
|
||||
|
||||
private void close() {
|
||||
for (var client : clients.values()) {
|
||||
client.getNetManager().shutdown();
|
||||
|
|
|
@ -8,3 +8,5 @@ server.tomcat.processor-cache=50
|
|||
starship-arena.gateway.host=127.0.0.1
|
||||
starship-arena.gateway.tcp-port=8081
|
||||
starship-arena.gateway.udp-port=8082
|
||||
starship-arena.gateway.init-timeout-ms=1000
|
||||
starship-arena.max-arenas=100
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
{
|
||||
"name": "Corvette",
|
||||
"components": [
|
||||
{
|
||||
"type": "panel",
|
||||
"name": "Main Fuselage",
|
||||
"mass": 5000,
|
||||
"points": [
|
||||
{"x": 0.3, "y": 0.6},
|
||||
{"x": 0.2, "y": 0.1},
|
||||
{"x": 0.1, "y": 0.5},
|
||||
{"x": 0.2, "y": 0.8},
|
||||
{"x": 0.8, "y": 0.8},
|
||||
{"x": 0.9, "y": 0.5},
|
||||
{"x": 0.8, "y": 0.1},
|
||||
{"x": 0.7, "y": 0.6}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "panel",
|
||||
"name": "Front Cargo Bay",
|
||||
"mass": 1000,
|
||||
"points": [
|
||||
{"x": 0.4, "y": 0.2},
|
||||
{"x": 0.35, "y": 0.6},
|
||||
{"x": 0.65, "y": 0.6},
|
||||
{"x": 0.6, "y": 0.2}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "cockpit",
|
||||
"mass": 800,
|
||||
"points": [
|
||||
{"x": 0.5, "y": 0.0},
|
||||
{"x": 0.4, "y": 0.2},
|
||||
{"x": 0.6, "y": 0.2}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "gun",
|
||||
"name": "Port-Side Machine Gun",
|
||||
"mass": 500,
|
||||
"location": {"x": 0.15, "y": 0.35},
|
||||
"rotation": 0,
|
||||
"minRotation": -160,
|
||||
"maxRotation": 5,
|
||||
"barrelWidth": 0.02,
|
||||
"barrelLength": 0.2
|
||||
},
|
||||
{
|
||||
"type": "gun",
|
||||
"name": "Starboard-Side Machine Gun",
|
||||
"mass": 500,
|
||||
"location": {"x": 0.85, "y": 0.35},
|
||||
"rotation": 0,
|
||||
"minRotation": -5,
|
||||
"maxRotation": 160,
|
||||
"barrelWidth": 0.02,
|
||||
"barrelLength": 0.2
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue