Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ members = [
]

[workspace.metadata]
protocol-version = 2
protocol-version = 3

[profile.release]
lto = true
Expand Down
2 changes: 1 addition & 1 deletion clients/jvm/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import com.google.protobuf.gradle.id

plugins {
id("java")
id("com.diffplug.spotless") version "7.0.0.BETA1"
id("com.diffplug.spotless") version "7.0.1"
id("com.google.protobuf") version "0.9.4"
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.atomic.cloud.common.cache;

import java.util.Optional;

public class CachedObject<T> {

public static final long DEFAULT_EXPIRATION = 1000 * 30;

private T value;
private long invalidateTime;

public synchronized Optional<T> getValue() {
if (this.value == null) return Optional.empty();
if (System.currentTimeMillis() > invalidateTime) {
this.value = null;
return Optional.empty();
}
return Optional.of(this.value);
}

public synchronized void setValue(T value) {
this.value = value;
this.invalidateTime = System.currentTimeMillis() + DEFAULT_EXPIRATION;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@
import com.google.protobuf.Empty;
import com.google.protobuf.StringValue;
import com.google.protobuf.UInt32Value;
import io.atomic.cloud.grpc.unit.ChannelManagement;
import io.atomic.cloud.grpc.unit.TransferManagement;
import io.atomic.cloud.grpc.unit.UnitServiceGrpc;
import io.atomic.cloud.grpc.unit.UserManagement;
import io.atomic.cloud.common.cache.CachedObject;
import io.atomic.cloud.grpc.unit.*;
import io.grpc.CallCredentials;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Metadata;
Expand All @@ -16,6 +14,7 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import lombok.Getter;
Expand All @@ -32,8 +31,9 @@ public class CloudConnection {
private UnitServiceGrpc.UnitServiceStub client;

// Cache values
private UInt32Value protocolVersion;
private StringValue controllerVersion;
private final CachedObject<UInt32Value> protocolVersion = new CachedObject<>();
private final CachedObject<StringValue> controllerVersion = new CachedObject<>();
private final CachedObject<UnitInformation.UnitListResponse> unitsInfo = new CachedObject<>();

public void connect() {
var channel = ManagedChannelBuilder.forAddress(this.address.getHost(), this.address.getPort());
Expand Down Expand Up @@ -127,36 +127,55 @@ public void subscribeToChannel(String channel, StreamObserver<ChannelManagement.
this.client.subscribeToChannel(StringValue.of(channel), observer);
}

public Optional<UnitInformation.UnitListResponse> getUnitsNow() {
var cached = this.unitsInfo.getValue();
if (cached.isEmpty()) {
this.getUnits(); // Request value from controller
}
return cached;
}

public CompletableFuture<UnitInformation.UnitListResponse> getUnits() {
var cached = this.unitsInfo.getValue();
if (cached.isPresent()) {
return CompletableFuture.completedFuture(cached.get());
}
var observer = new StreamObserverImpl<UnitInformation.UnitListResponse>();
this.client.getUnits(Empty.getDefaultInstance(), observer);
return observer.future().thenApply((value) -> {
this.unitsInfo.setValue(value);
return value;
});
}

public CompletableFuture<Empty> sendReset() {
var observer = new StreamObserverImpl<Empty>();
this.client.reset(Empty.getDefaultInstance(), observer);
return observer.future();
}

public synchronized CompletableFuture<UInt32Value> getProtocolVersion() {
if (this.controllerVersion != null) {
return CompletableFuture.completedFuture(this.protocolVersion);
var cached = this.protocolVersion.getValue();
if (cached.isPresent()) {
return CompletableFuture.completedFuture(cached.get());
}
var observer = new StreamObserverImpl<UInt32Value>();
this.client.getProtocolVersion(Empty.getDefaultInstance(), observer);
return observer.future().thenApply((value) -> {
synchronized (this) {
this.protocolVersion = value;
}
this.protocolVersion.setValue(value);
return value;
});
}

public synchronized CompletableFuture<StringValue> getControllerVersion() {
if (this.controllerVersion != null) {
return CompletableFuture.completedFuture(this.controllerVersion);
var cached = this.controllerVersion.getValue();
if (cached.isPresent()) {
return CompletableFuture.completedFuture(cached.get());
}
var observer = new StreamObserverImpl<StringValue>();
this.client.getControllerVersion(Empty.getDefaultInstance(), observer);
return observer.future().thenApply((value) -> {
synchronized (this) {
this.controllerVersion = value;
}
this.controllerVersion.setValue(value);
return value;
});
}
Expand Down
10 changes: 5 additions & 5 deletions clients/jvm/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ archive_basename=client
client_version=0.3.0

## Minecraft
minecraft_version=1.21.1
minecraft_version=1.21.4

## Java
lombok_version=1.18.34
jetbrains_annotations_version=24.0.0
lombok_version=1.18.36
jetbrains_annotations_version=26.0.1

## gRPC
grpc_version=1.65.0
protobuf_version=4.27.2
grpc_version=1.69.0
protobuf_version=4.29.2
Binary file modified clients/jvm/gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
2 changes: 1 addition & 1 deletion clients/jvm/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Expand Down
4 changes: 3 additions & 1 deletion clients/jvm/gradlew
100644 → 100755

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions clients/jvm/gradlew.bat

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions clients/jvm/paper/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@

plugins {
// Paper
id("io.papermc.paperweight.userdev") version "1.7.1"
id("io.papermc.paperweight.userdev") version "2.0.0-beta.12"

// Shadow (Only for including the API files into the jar)
id("com.gradleup.shadow") version "8.3.0"
id("com.gradleup.shadow") version "9.0.0-beta4"
}

dependencies {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package io.atomic.cloud.paper;

import io.atomic.cloud.paper.command.CloudCommand;
import io.atomic.cloud.paper.command.SendCommand;
import io.papermc.paper.plugin.bootstrap.BootstrapContext;
import io.papermc.paper.plugin.bootstrap.PluginBootstrap;
import io.papermc.paper.plugin.bootstrap.PluginProviderContext;
import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;

@ApiStatus.Experimental
@SuppressWarnings("UnstableApiUsage")
public class CloudPluginBootstrap implements PluginBootstrap {

@Override
Expand All @@ -28,6 +28,7 @@ private void registerCommands(@NotNull BootstrapContext context) {
manager.registerEventHandler(LifecycleEvents.COMMANDS, event -> {
final var commands = event.registrar();
CloudCommand.register(commands);
SendCommand.register(commands);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import net.kyori.adventure.text.format.NamedTextColor;
import org.jetbrains.annotations.NotNull;

@SuppressWarnings("UnstableApiUsage")
public class CloudCommand {

public static void register(@NotNull Commands commands) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package io.atomic.cloud.paper.command;

import com.mojang.brigadier.Command;
import io.atomic.cloud.grpc.unit.TransferManagement;
import io.atomic.cloud.grpc.unit.UnitInformation;
import io.atomic.cloud.paper.CloudPlugin;
import io.atomic.cloud.paper.command.argument.UnitArgument;
import io.atomic.cloud.paper.permission.Permissions;
import io.papermc.paper.command.brigadier.Commands;
import io.papermc.paper.command.brigadier.argument.ArgumentTypes;
import io.papermc.paper.command.brigadier.argument.resolvers.selector.PlayerSelectorArgumentResolver;
import java.util.concurrent.CompletableFuture;
import org.jetbrains.annotations.NotNull;

@SuppressWarnings("UnstableApiUsage")
public class SendCommand {

public static void register(@NotNull Commands commands) {
commands.register(Commands.literal("send")
.requires(Permissions.SERVER_COMMAND::check)
.then(Commands.argument("user", ArgumentTypes.players())
.then(Commands.argument("unit", UnitArgument.INSTANCE).executes(context -> {
var sender = context.getSource().getSender();
var connection = CloudPlugin.INSTANCE.connection();

var users = context.getArgument("user", PlayerSelectorArgumentResolver.class)
.resolve(context.getSource());
var unit = context.getArgument("unit", UnitInformation.SimpleUnitValue.class);
var userCount = users.size();

sender.sendRichMessage("<gray>Transferring <aqua>" + userCount
+ " <gray>users to unit <blue>" + unit.getName() + "<dark_gray>...");
CompletableFuture.allOf(users.stream()
.map((player) -> connection.transferUser(
TransferManagement.TransferUserRequest.newBuilder()
.setUserUuid(player.getUniqueId()
.toString())
.setTarget(
TransferManagement.TransferTargetValue.newBuilder()
.setTargetType(
TransferManagement
.TransferTargetValue
.TargetType.UNIT)
.setTarget(unit.getUuid()))
.build()))
.toArray(CompletableFuture[]::new))
.whenComplete((result, throwable) -> {
if (throwable != null) {
sender.sendRichMessage(
"<red>Failed to transfer " + userCount + " users to unit "
+ unit.getName() + ": " + throwable.getMessage());
} else {
sender.sendRichMessage("<green>Submitted <aqua>" + userCount
+ " <gray>transfer requests to controller");
}
});
return Command.SINGLE_SUCCESS;
})))
.build());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package io.atomic.cloud.paper.command.argument;

import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import io.atomic.cloud.grpc.unit.UnitInformation;
import io.atomic.cloud.paper.CloudPlugin;
import io.papermc.paper.command.brigadier.MessageComponentSerializer;
import io.papermc.paper.command.brigadier.argument.CustomArgumentType;
import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.CompletableFuture;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;

@SuppressWarnings("UnstableApiUsage")
public class UnitArgument implements CustomArgumentType.Converted<UnitInformation.SimpleUnitValue, String> {

public static final UnitArgument INSTANCE = new UnitArgument();
private static final Collection<String> EXAMPLES = Arrays.asList("lobby-1", "bedwars-2x1-1");

@Override
public UnitInformation.@NotNull SimpleUnitValue convert(@NotNull String value) throws CommandSyntaxException {
var cached = CloudPlugin.INSTANCE.connection().getUnitsNow();
if (cached.isEmpty()) throw createException("Fetching available units...");
var unit = cached.get().getUnitsList().stream()
.filter(item -> item.getName().equals(value))
.findFirst();
if (unit.isEmpty()) throw createException("\"" + value + "\" does not exist");
return unit.get();
}

@Override
public <S> @NotNull CompletableFuture<Suggestions> listSuggestions(
@NotNull CommandContext<S> context, @NotNull SuggestionsBuilder builder) {
return CloudPlugin.INSTANCE.connection().getUnits().thenCompose(response -> {
response.getUnitsList()
.forEach(unit -> builder.suggest(
unit.getName(),
MessageComponentSerializer.message()
.serialize(Component.text(unit.getUuid()).color(NamedTextColor.BLUE))));
return builder.buildFuture();
});
}

@Override
public @NotNull ArgumentType<String> getNativeType() {
return StringArgumentType.word();
}

@Override
public @NotNull Collection<String> getExamples() {
return EXAMPLES;
}

@Contract("_ -> new")
private @NotNull CommandSyntaxException createException(@NotNull String message) {
return new CommandSyntaxException(new SimpleCommandExceptionType(() -> message), () -> message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
@AllArgsConstructor
@Getter
public enum Permissions {
CLOUD_COMMAND("atomic.cloud.command.cloud");
CLOUD_COMMAND("atomic.cloud.command.cloud"),
SERVER_COMMAND("atomic.cloud.command.server");

private final String permission;

Expand Down
2 changes: 1 addition & 1 deletion controller/src/application/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,4 +148,4 @@ impl Hash for EventKey {
}*/
}
}
}
}
Loading