This commit is contained in:
kento2 2026-06-01 21:12:24 +02:00
parent f9ceea47d6
commit e87ccbb7cb
16 changed files with 288 additions and 33 deletions

View file

@ -8,7 +8,7 @@ plugins {
}
group = "de.kentoj.scrow"
version = "0.2"
version = "0.3"
repositories {
mavenCentral()

View file

@ -1,8 +1,10 @@
package de.kentoj.scrowlib.messaging;
import de.kentoj.scrowlib.messaging.respose.SafeResult;
import io.nats.client.Connection;
import io.nats.client.Dispatcher;
import io.nats.client.Message;
import org.bson.Document;
import org.jetbrains.annotations.NotNull;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@ -18,23 +20,34 @@ public class NatsRepository {
this.dispatcher = nats.createDispatcher();
}
public Mono<@NotNull Void> publish(String subject, byte[] data) {
public Mono<@NotNull Void> publishSafeResult(String subject, SafeResult safeResult) {
return this.publish(subject, safeResult.toDocument());
}
public Mono<@NotNull Void> publish(String subject, Document document) {
return publishRaw(subject, DocumentRepository.toBytes(document));
}
public Mono<@NotNull Void> publishRaw(String subject, byte[] data) {
return Mono.fromRunnable(() -> nats.publish(subject, data));
}
public Flux<@NotNull Message> subscribe(String subject) {
return Flux.create(sink -> {
var handler = dispatcher.subscribe(subject, msg -> {
try {
sink.next(msg);
} catch (Throwable e) {
sink.error(e);
}
});
var handler = dispatcher.subscribe(subject, msg -> {
try {
sink.next(msg);
} catch (Throwable e) {
sink.error(e);
}
});
sink.onCancel(handler::unsubscribe);
sink.onDispose(handler::unsubscribe);
});
}
sink.onCancel(handler::unsubscribe);
sink.onDispose(handler::unsubscribe);
});
public Mono<@NotNull Message> request(String subject, Document document) {
return Mono.fromFuture(nats.request(subject, DocumentRepository.toBytes(document)));
}
public Mono<@NotNull Message> request(String subject, byte[] data) {

View file

@ -0,0 +1,34 @@
package de.kentoj.scrowlib.messaging.respose;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.bson.Document;
@Getter
@AllArgsConstructor(access = AccessLevel.PACKAGE)
public final class SafeErrorResult extends SafeResult {
private final String type;
private final String message;
public SafeErrorResult(Throwable throwable) {
this.type = throwable.getClass().getCanonicalName();
this.message = throwable.getMessage();
}
@Override
public Document unwrap() {
throw this.toException();
}
public RuntimeException toException() {
return new RuntimeException(message);
}
@Override
public Document toDocument() {
return new Document("error", new Document()
.append("type", this.type)
.append("message", this.getMessage()));
}
}

View file

@ -0,0 +1,64 @@
package de.kentoj.scrowlib.messaging.respose;
import de.kentoj.scrowlib.messaging.DocumentRepository;
import io.nats.client.Message;
import org.bson.Document;
public abstract sealed class SafeResult permits SafeErrorResult, SafeSuccessResult {
/**
* @return the SafeResult as a Document for transit
*/
public abstract Document toDocument();
/**
* @return the data stored in the SafeResult
* @throws RuntimeException if the SafeResult represents an error
*/
public abstract Document unwrap();
public boolean isError() {
return this instanceof SafeErrorResult;
}
public boolean isSuccess() {
return this instanceof SafeSuccessResult;
}
public SafeErrorResult asError() {
return (SafeErrorResult) this;
}
public SafeSuccessResult asSuccess() {
return (SafeSuccessResult) this;
}
public static Document unwrap(Message message) {
return fromMessage(message).unwrap();
}
public static SafeResult wrap(Document document) {
return new SafeSuccessResult(document);
}
public static SafeResult wrap(Throwable throwable) {
return new SafeErrorResult(throwable);
}
public static SafeResult fromMessage(Message message) {
return fromDocument(DocumentRepository.fromMessage(message));
}
public static SafeResult fromDocument(Document document) {
if (document.containsKey("error")) {
var doc = document.get("error", Document.class);
return new SafeErrorResult(
doc.getString("type"),
doc.getString("message")
);
} else if (document.containsKey("data")) {
return new SafeSuccessResult(document.get("data", Document.class));
}
throw new IllegalArgumentException("document does not conform to SafeDocument format");
}
}

View file

@ -0,0 +1,22 @@
package de.kentoj.scrowlib.messaging.respose;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.bson.Document;
@Getter
@AllArgsConstructor(access = AccessLevel.PACKAGE)
public final class SafeSuccessResult extends SafeResult {
private final Document data;
@Override
public Document unwrap() {
return this.getData();
}
@Override
public Document toDocument() {
return new Document("data", this.getData());
}
}

View file

@ -1,16 +1,26 @@
package de.kentoj.scrowlib.servermanager;
import lombok.AllArgsConstructor;
import com.google.common.base.Preconditions;
import lombok.Value;
@Value
@AllArgsConstructor
public class ServerInstance {
String templateName;
String handle;
int port;
String state;
public ServerInstance(String handle, int port) {
this(handle, port, "STARTING");
public ServerInstance(String templateName, String handle, int port, String state) {
Preconditions.checkNotNull(templateName);
Preconditions.checkNotNull(handle);
Preconditions.checkNotNull(state);
this.templateName = templateName;
this.handle = handle;
this.port = port;
this.state = state;
}
public ServerInstance(String templateName, String handle, int port) {
this(templateName, handle, port, "STARTING");
}
}

View file

@ -4,6 +4,8 @@ import org.jetbrains.annotations.NotNull;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.UUID;
public interface ServerManager {
/**
@ -24,4 +26,6 @@ public interface ServerManager {
Mono<@NotNull ServerInstance> getInfo(String handle);
Mono<@NotNull Void> setState(String handle, String state);
Mono<@NotNull Void> sendPlayer(UUID playerId, String serverHandle);
}

View file

@ -3,17 +3,20 @@ package de.kentoj.scrowlib.servermanager;
import com.google.common.base.Preconditions;
import de.kentoj.scrowlib.messaging.DocumentRepository;
import de.kentoj.scrowlib.messaging.NatsRepository;
import de.kentoj.scrowlib.messaging.respose.SafeResult;
import io.nats.client.Connection;
import org.bson.Document;
import org.jetbrains.annotations.NotNull;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public class CrownServerManager implements ServerManager {
import java.util.UUID;
public class ServerManagerImpl implements ServerManager {
private final NatsRepository natsRepo;
public CrownServerManager(Connection nats) {
public ServerManagerImpl(Connection nats) {
this.natsRepo = new NatsRepository(nats);
}
@ -23,6 +26,7 @@ public class CrownServerManager implements ServerManager {
return natsRepo.request("crown.deploy-server", payload)
.map(DocumentRepository::fromMessage)
.map(doc -> new ServerInstance(
template,
doc.getString("handle"),
doc.getInteger("port")
));
@ -38,6 +42,7 @@ public class CrownServerManager implements ServerManager {
)
.flatMapIterable(l -> l)
.map(doc -> new ServerInstance(
doc.getString("templateName"),
doc.getString("handle"),
doc.getInteger("port"),
doc.getString("state")
@ -57,6 +62,7 @@ public class CrownServerManager implements ServerManager {
return natsRepo.request("crown.get-info", payload)
.map(DocumentRepository::fromMessage)
.map(doc -> new ServerInstance(
doc.getString("templateName"),
handle,
doc.getInteger("port"),
doc.getString("state")
@ -72,4 +78,14 @@ public class CrownServerManager implements ServerManager {
return natsRepo.request("crown.set-state", payload)
.then();
}
@Override
public Mono<@NotNull Void> sendPlayer(UUID playerId, String serverHandle) {
var payload = DocumentRepository.toBytes(new Document()
.append("playerId", playerId.toString())
.append("handle", serverHandle));
return natsRepo.request("proxy.send-player", payload)
.map(SafeResult::unwrap)
.then();
}
}