1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-01-18 21:22:56 +00:00

Start work on curtailing our global state

The last 4 or 5 commits have simplified things. I can now have some
unnecessary complexity as a treat!

This is some initial work on better tying the lifecycle of
computers (and ComputerCraft) related state to the lifecycle of the
current Minecraft server.

 - Move server-wide methods in IComputerEnvironment (such as creating
   resource mounts) into a separate interface.
 - Add a new ServerContext class, which now holds the ID Assigner,
   server computer registry, and some other tiny bits and bobs. This can
   only be accessed by ServerContect.get(MinecraftServer), forcing
   slightly better discipline for how we use these globals.

This does allow us to nuke some of the ugliest bits in IDAssigner. Even
if it makes things much longer!
This commit is contained in:
Jonathan Coates 2022-10-21 21:01:01 +01:00
parent cee60cdb5b
commit b663028f42
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
29 changed files with 412 additions and 193 deletions

View File

@ -24,13 +24,13 @@ import dan200.computercraft.core.asm.GenericMethod;
import dan200.computercraft.core.filesystem.FileMount;
import dan200.computercraft.core.filesystem.ResourceMount;
import dan200.computercraft.shared.*;
import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.peripheral.generic.GenericPeripheralProvider;
import dan200.computercraft.shared.peripheral.generic.data.DetailProviders;
import dan200.computercraft.shared.peripheral.modem.wireless.WirelessNetwork;
import dan200.computercraft.shared.util.IDAssigner;
import dan200.computercraft.shared.wired.WiredNode;
import net.minecraft.resources.IReloadableResourceManager;
import net.minecraft.resources.IResourceManager;
import net.minecraft.server.MinecraftServer;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.Direction;
import net.minecraft.util.ResourceLocation;
@ -59,9 +59,9 @@ public final class ComputerCraftAPIImpl implements IComputerCraftAPI
{
}
public static InputStream getResourceFile( String domain, String subPath )
public static InputStream getResourceFile( MinecraftServer server, String domain, String subPath )
{
IReloadableResourceManager manager = (IReloadableResourceManager) ServerLifecycleHooks.getCurrentServer().getDataPackRegistries().getResourceManager();
IResourceManager manager = server.getDataPackRegistries().getResourceManager();
try
{
return manager.getResource( new ResourceLocation( domain, subPath ) ).getInputStream();
@ -85,7 +85,7 @@ public final class ComputerCraftAPIImpl implements IComputerCraftAPI
@Override
public int createUniqueNumberedSaveDir( @Nonnull World world, @Nonnull String parentSubPath )
{
return IDAssigner.getNextId( parentSubPath );
return ServerContext.get( world.getServer() ).getNextId( parentSubPath );
}
@Override
@ -93,7 +93,7 @@ public final class ComputerCraftAPIImpl implements IComputerCraftAPI
{
try
{
return new FileMount( new File( IDAssigner.getDir(), subPath ), capacity );
return new FileMount( new File( ServerContext.get( world.getServer() ).storageDir().toFile(), subPath ), capacity );
}
catch( Exception e )
{

View File

@ -207,7 +207,7 @@ public class HTTPAPI implements ILuaAPI
if( !headers.contains( HttpHeaderNames.USER_AGENT ) )
{
headers.set( HttpHeaderNames.USER_AGENT, apiEnvironment.getComputerEnvironment().getUserAgent() );
headers.set( HttpHeaderNames.USER_AGENT, apiEnvironment.getGlobalEnvironment().getUserAgent() );
}
return headers;
}

View File

@ -7,8 +7,9 @@ package dan200.computercraft.core.apis;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.IWorkMonitor;
import dan200.computercraft.core.computer.ComputerEnvironment;
import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.core.computer.IComputerEnvironment;
import dan200.computercraft.core.computer.GlobalEnvironment;
import dan200.computercraft.core.filesystem.FileSystem;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.tracking.TrackingField;
@ -29,7 +30,10 @@ public interface IAPIEnvironment
int getComputerID();
@Nonnull
IComputerEnvironment getComputerEnvironment();
ComputerEnvironment getComputerEnvironment();
@Nonnull
GlobalEnvironment getGlobalEnvironment();
@Nonnull
IWorkMonitor getMainThreadMonitor();

View File

@ -9,7 +9,7 @@ import dan200.computercraft.api.lua.IArguments;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.core.computer.IComputerEnvironment;
import dan200.computercraft.core.computer.ComputerEnvironment;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.shared.util.Colour;
@ -24,7 +24,7 @@ import javax.annotation.Nonnull;
public class TermAPI extends TermMethods implements ILuaAPI
{
private final Terminal terminal;
private final IComputerEnvironment environment;
private final ComputerEnvironment environment;
public TermAPI( IAPIEnvironment environment )
{

View File

@ -36,7 +36,8 @@ public class Computer
private String label = null;
// Read-only fields about the computer
private final IComputerEnvironment environment;
private final ComputerEnvironment computerEnvironment;
private final GlobalEnvironment globalEnvironment;
private final Terminal terminal;
private final ComputerExecutor executor;
private final MainThreadExecutor serverExecutor;
@ -49,20 +50,26 @@ public class Computer
private boolean startRequested;
private int ticksSinceStart = -1;
public Computer( IComputerEnvironment environment, Terminal terminal, int id )
public Computer( GlobalEnvironment globalEnvironment, ComputerEnvironment environment, Terminal terminal, int id )
{
if( id < 0 ) throw new IllegalStateException( "Id has not been assigned" );
this.id = id;
this.environment = environment;
this.globalEnvironment = globalEnvironment;
this.computerEnvironment = environment;
this.terminal = terminal;
executor = new ComputerExecutor( this );
serverExecutor = new MainThreadExecutor( this );
}
IComputerEnvironment getComputerEnvironment()
ComputerEnvironment getComputerEnvironment()
{
return environment;
return computerEnvironment;
}
GlobalEnvironment getGlobalEnvironment()
{
return globalEnvironment;
}
FileSystem getFileSystem()

View File

@ -0,0 +1,37 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.computer;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.core.filesystem.FileMount;
import javax.annotation.Nullable;
public interface ComputerEnvironment
{
/**
* Get the current in-game day.
*
* @return The current day.
*/
int getDay();
/**
* Get the current in-game time of day.
*
* @return The current time.
*/
double getTimeOfDay();
/**
* Construct the mount for this computer's user-writable data.
*
* @return The constructed mount or {@code null} if the mount could not be created.
* @see FileMount
*/
@Nullable
IWritableMount createRootMount();
}

View File

@ -340,7 +340,7 @@ final class ComputerExecutor
private IMount getRomMount()
{
return computer.getComputerEnvironment().createResourceMount( "computercraft", "lua/rom" );
return computer.getGlobalEnvironment().createResourceMount( "computercraft", "lua/rom" );
}
private IWritableMount getRootMount()
@ -382,7 +382,7 @@ final class ComputerExecutor
InputStream biosStream = null;
try
{
biosStream = computer.getComputerEnvironment().createResourceFile( "computercraft", "lua/bios.lua" );
biosStream = computer.getGlobalEnvironment().createResourceFile( "computercraft", "lua/bios.lua" );
}
catch( Exception ignored )
{

View File

@ -73,11 +73,18 @@ public final class Environment implements IAPIEnvironment
@Nonnull
@Override
public IComputerEnvironment getComputerEnvironment()
public ComputerEnvironment getComputerEnvironment()
{
return computer.getComputerEnvironment();
}
@Nonnull
@Override
public GlobalEnvironment getGlobalEnvironment()
{
return computer.getGlobalEnvironment();
}
@Nonnull
@Override
public IWorkMonitor getMainThreadMonitor()

View File

@ -0,0 +1,53 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.computer;
import dan200.computercraft.api.filesystem.IMount;
import javax.annotation.Nullable;
import java.io.InputStream;
/**
* The global environment in which computers reside.
*/
public interface GlobalEnvironment
{
/**
* Get a "host" string describing the program hosting CC. It should be of the form {@literal ComputerCraft
* $CC_VERSION ($HOST)}, where {@literal $HOST} is a user-defined string such as {@literal Minecraft 1.19}.
*
* @return The host string.
*/
String getHostString();
/**
* Get the HTTP user-agent to use for requests. This should be similar to {@link #getHostString()} , but in the form
* of a HTTP User-Agent.
*
* @return The HTTP
*/
String getUserAgent();
/**
* Create a mount from mod-provided resources.
*
* @param domain The domain (i.e. mod id) providing resources.
* @param subPath The path to these resources under the domain.
* @return The created mount or {@code null} if it could not be created.
*/
@Nullable
IMount createResourceMount( String domain, String subPath );
/**
* Open a single mod-provided file.
*
* @param domain The domain (i.e. mod id) providing resources.
* @param subPath The path to these files under the domain.
* @return The opened file or {@code null} if it could not be opened.
*/
@Nullable
InputStream createResourceFile( String domain, String subPath );
}

View File

@ -1,35 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.computer;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.InputStream;
public interface IComputerEnvironment
{
int getDay();
double getTimeOfDay();
@Nonnull
String getHostString();
@Nonnull
String getUserAgent();
@Nullable
IWritableMount createRootMount();
@Nullable
IMount createResourceMount( String domain, String subPath );
@Nullable
InputStream createResourceFile( String domain, String subPath );
}

View File

@ -110,7 +110,7 @@ public class CobaltLuaMachine implements ILuaMachine
// Add version globals
globals.rawset( "_VERSION", valueOf( "Lua 5.1" ) );
globals.rawset( "_HOST", valueOf( computer.getAPIEnvironment().getComputerEnvironment().getHostString() ) );
globals.rawset( "_HOST", valueOf( computer.getAPIEnvironment().getGlobalEnvironment().getHostString() ) );
globals.rawset( "_CC_DEFAULT_SETTINGS", valueOf( ComputerCraft.defaultComputerSettings ) );
if( ComputerCraft.disableLua51Features )
{

View File

@ -12,7 +12,7 @@ import dan200.computercraft.core.filesystem.ResourceMount;
import dan200.computercraft.core.tracking.ComputerMBean;
import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.shared.command.CommandComputerCraft;
import dan200.computercraft.shared.computer.core.ServerComputerRegistry;
import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.peripheral.modem.wireless.WirelessNetwork;
import net.minecraft.entity.EntityType;
import net.minecraft.loot.ConstantRange;
@ -27,6 +27,7 @@ import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.server.FMLServerStartingEvent;
import net.minecraftforge.fml.event.server.FMLServerStoppedEvent;
import net.minecraftforge.fml.server.ServerLifecycleHooks;
import java.util.Arrays;
import java.util.HashSet;
@ -52,7 +53,7 @@ public final class CommonHooks
if( event.phase == TickEvent.Phase.START )
{
MainThread.executePendingTasks();
ServerComputerRegistry.INSTANCE.update();
ServerContext.get( ServerLifecycleHooks.getCurrentServer() ).registry().update();
}
}
@ -72,6 +73,7 @@ public final class CommonHooks
}
resetState();
ServerContext.create( server );
ComputerMBean.registerTracker();
}
@ -83,7 +85,7 @@ public final class CommonHooks
private static void resetState()
{
ServerComputerRegistry.INSTANCE.reset();
ServerContext.close();
MainThread.reset();
WirelessNetwork.resetNetworks();
Tracking.reset();

View File

@ -6,14 +6,14 @@
package dan200.computercraft.shared.command;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.util.IDAssigner;
import dan200.computercraft.shared.computer.core.ServerContext;
import net.minecraft.client.Minecraft;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.Util;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.ClientChatEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.server.ServerLifecycleHooks;
import java.io.File;
@ -37,8 +37,8 @@ public final class ClientCommands
// Emulate the command on the client side
if( event.getMessage().startsWith( OPEN_COMPUTER ) )
{
MinecraftServer server = ServerLifecycleHooks.getCurrentServer();
if( server == null || server.isDedicatedServer() ) return;
MinecraftServer server = Minecraft.getInstance().getSingleplayerServer();
if( server == null ) return;
event.setCanceled( true );
@ -53,7 +53,7 @@ public final class ClientCommands
return;
}
File file = new File( IDAssigner.getDir(), "computer/" + id );
File file = new File( ServerContext.get( server ).storageDir().toFile(), "computer/" + id );
if( !file.isDirectory() ) return;
Util.getPlatform().openFile( file );

View File

@ -18,10 +18,9 @@ import dan200.computercraft.core.tracking.TrackingField;
import dan200.computercraft.shared.command.text.TableBuilder;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.core.ServerComputerRegistry;
import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.computer.inventory.ContainerViewComputer;
import dan200.computercraft.shared.network.container.ComputerContainerData;
import dan200.computercraft.shared.util.IDAssigner;
import net.minecraft.command.CommandSource;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity;
@ -75,7 +74,7 @@ public final class CommandComputerCraft
TableBuilder table = new TableBuilder( DUMP_LIST_ID, "Computer", "On", "Position" );
CommandSource source = context.getSource();
List<ServerComputer> computers = new ArrayList<>( ServerComputerRegistry.INSTANCE.getComputers() );
List<ServerComputer> computers = new ArrayList<>( ServerContext.get( source.getServer() ).registry().getComputers() );
// Unless we're on a server, limit the number of rows we can send.
World world = source.getLevel();
@ -140,7 +139,7 @@ public final class CommandComputerCraft
.then( command( "shutdown" )
.requires( UserLevel.OWNER_OP )
.argManyValue( "computers", manyComputers(), s -> ServerComputerRegistry.INSTANCE.getComputers() )
.argManyValue( "computers", manyComputers(), s -> ServerContext.get( s.getServer() ).registry().getComputers() )
.executes( ( context, computerSelectors ) -> {
int shutdown = 0;
Set<ServerComputer> computers = unwrap( context.getSource(), computerSelectors );
@ -155,7 +154,7 @@ public final class CommandComputerCraft
.then( command( "turn-on" )
.requires( UserLevel.OWNER_OP )
.argManyValue( "computers", manyComputers(), s -> ServerComputerRegistry.INSTANCE.getComputers() )
.argManyValue( "computers", manyComputers(), s -> ServerContext.get( s.getServer() ).registry().getComputers() )
.executes( ( context, computerSelectors ) -> {
int on = 0;
Set<ServerComputer> computers = unwrap( context.getSource(), computerSelectors );
@ -327,7 +326,7 @@ public final class CommandComputerCraft
if( UserLevel.OWNER.test( source ) && isPlayer( source ) )
{
ITextComponent linkPath = linkStorage( computerId );
ITextComponent linkPath = linkStorage( source, computerId );
if( linkPath != null ) out.append( " " ).append( linkPath );
}
@ -350,9 +349,9 @@ public final class CommandComputerCraft
}
}
private static ITextComponent linkStorage( int id )
private static ITextComponent linkStorage( CommandSource source, int id )
{
File file = new File( IDAssigner.getDir(), "computer/" + id );
File file = new File( ServerContext.get( source.getServer() ).storageDir().toFile(), "computer/" + id );
if( !file.isDirectory() ) return null;
return link(
@ -382,7 +381,7 @@ public final class CommandComputerCraft
Map<Computer, ServerComputer> lookup = new HashMap<>();
int maxId = 0, maxInstance = 0;
for( ServerComputer server : ServerComputerRegistry.INSTANCE.getComputers() )
for( ServerComputer server : ServerContext.get( source.getServer() ).registry().getComputers() )
{
lookup.put( server.getComputer(), server );

View File

@ -14,7 +14,7 @@ import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.core.ServerComputerRegistry;
import dan200.computercraft.shared.computer.core.ServerContext;
import net.minecraft.command.CommandSource;
import net.minecraft.command.arguments.IArgumentSerializer;
import net.minecraft.network.PacketBuffer;
@ -89,7 +89,7 @@ public final class ComputersArgumentType implements ArgumentType<ComputersArgume
{
int instance = reader.readInt();
computers = s -> {
ServerComputer computer = ServerComputerRegistry.INSTANCE.get( instance );
ServerComputer computer = ServerContext.get( s.getServer() ).registry().get( instance );
return computer == null ? Collections.emptyList() : Collections.singletonList( computer );
};
}
@ -124,18 +124,18 @@ public final class ComputersArgumentType implements ArgumentType<ComputersArgume
return suggestOnServer( context, builder, s -> {
if( remaining.startsWith( "@" ) )
{
suggestComputers( builder, remaining, x -> {
suggestComputers( s.getSource(), builder, remaining, x -> {
String label = x.getLabel();
return label == null ? null : "@" + label;
} );
}
else if( remaining.startsWith( "#" ) )
{
suggestComputers( builder, remaining, c -> "#" + c.getID() );
suggestComputers( s.getSource(), builder, remaining, c -> "#" + c.getID() );
}
else
{
suggestComputers( builder, remaining, c -> Integer.toString( c.getInstanceID() ) );
suggestComputers( s.getSource(), builder, remaining, c -> Integer.toString( c.getInstanceID() ) );
}
return builder.buildFuture();
@ -148,10 +148,10 @@ public final class ComputersArgumentType implements ArgumentType<ComputersArgume
return EXAMPLES;
}
private static void suggestComputers( SuggestionsBuilder builder, String remaining, Function<ServerComputer, String> renderer )
private static void suggestComputers( CommandSource source, SuggestionsBuilder builder, String remaining, Function<ServerComputer, String> renderer )
{
remaining = remaining.toLowerCase( Locale.ROOT );
for( ServerComputer computer : ServerComputerRegistry.INSTANCE.getComputers() )
for( ServerComputer computer : ServerContext.get( source.getServer() ).registry().getComputers() )
{
String converted = renderer.apply( computer );
if( converted != null && converted.toLowerCase( Locale.ROOT ).startsWith( remaining ) )
@ -163,7 +163,7 @@ public final class ComputersArgumentType implements ArgumentType<ComputersArgume
private static ComputersSupplier getComputers( Predicate<ServerComputer> predicate )
{
return s -> Collections.unmodifiableList( ServerComputerRegistry.INSTANCE
return s -> Collections.unmodifiableList( ServerContext.get( s.getServer() ).registry()
.getComputers()
.stream()
.filter( predicate )

View File

@ -14,7 +14,7 @@ import dan200.computercraft.shared.common.TileGeneric;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ComputerState;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.core.ServerComputerRegistry;
import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.network.container.ComputerContainerData;
import dan200.computercraft.shared.util.DirectionUtil;
import dan200.computercraft.shared.util.IDAssigner;
@ -378,7 +378,7 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
boolean changed = false;
ServerComputer computer = ServerComputerRegistry.INSTANCE.get( instanceID );
ServerComputer computer = ServerContext.get( getLevel().getServer() ).registry().get( instanceID );
if( computer == null )
{
if( computerID < 0 )
@ -400,7 +400,7 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
@Nullable
public ServerComputer getServerComputer()
{
return getLevel().isClientSide ? null : ServerComputerRegistry.INSTANCE.get( instanceID );
return getLevel().isClientSide ? null : ServerContext.get( getLevel().getServer() ).registry().get( instanceID );
}
// Networking stuff

View File

@ -6,16 +6,14 @@
package dan200.computercraft.shared.computer.core;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.ComputerCraftAPIImpl;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.apis.IAPIEnvironment;
import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.computer.ComputerEnvironment;
import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.core.computer.IComputerEnvironment;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.shared.computer.menu.ComputerMenu;
import dan200.computercraft.shared.network.NetworkHandler;
@ -27,14 +25,11 @@ import net.minecraft.inventory.container.Container;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.server.ServerWorld;
import net.minecraftforge.versions.mcp.MCPVersion;
import javax.annotation.Nonnull;
import java.io.InputStream;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
public class ServerComputer implements InputHandler, IComputerEnvironment
public class ServerComputer implements InputHandler, ComputerEnvironment
{
private final int instanceID;
@ -55,10 +50,11 @@ public class ServerComputer implements InputHandler, IComputerEnvironment
this.world = world;
this.family = family;
instanceID = ServerComputerRegistry.INSTANCE.getUnusedInstanceID();
ServerContext context = ServerContext.get( world.getServer() );
instanceID = context.registry().getUnusedInstanceID();
terminal = new Terminal( terminalWidth, terminalHeight, family != ComputerFamily.NORMAL, this::markTerminalChanged );
computer = new Computer( this, terminal, computerID );
computer = new Computer( context.environment(), this, terminal, computerID );
computer.setLabel( label );
}
@ -140,7 +136,7 @@ public class ServerComputer implements InputHandler, IComputerEnvironment
public int register()
{
ServerComputerRegistry.INSTANCE.add( instanceID, this );
ServerContext.get( world.getServer() ).registry().add( instanceID, this );
return instanceID;
}
@ -152,7 +148,7 @@ public class ServerComputer implements InputHandler, IComputerEnvironment
public void close()
{
unload();
ServerComputerRegistry.INSTANCE.remove( instanceID );
ServerContext.get( world.getServer() ).registry().remove( instanceID );
}
private void sendToAllInteracting( Function<Container, NetworkMessage> createPacket )
@ -285,30 +281,4 @@ public class ServerComputer implements InputHandler, IComputerEnvironment
{
return ComputerCraftAPI.createSaveDirMount( world, "computer/" + computer.getID(), ComputerCraft.computerSpaceLimit );
}
@Override
public IMount createResourceMount( String domain, String subPath )
{
return ComputerCraftAPI.createResourceMount( domain, subPath );
}
@Override
public InputStream createResourceFile( String domain, String subPath )
{
return ComputerCraftAPIImpl.getResourceFile( domain, subPath );
}
@Nonnull
@Override
public String getHostString()
{
return String.format( "ComputerCraft %s (Minecraft %s)", ComputerCraftAPI.getInstalledVersion(), MCPVersion.getMCVersion() );
}
@Nonnull
@Override
public String getUserAgent()
{
return ComputerCraft.MOD_ID + "/" + ComputerCraftAPI.getInstalledVersion();
}
}

View File

@ -16,9 +16,8 @@ import java.util.Random;
public class ServerComputerRegistry
{
private static final Random RANDOM = new Random();
public static final ServerComputerRegistry INSTANCE = new ServerComputerRegistry();
private int sessionId = RANDOM.nextInt();
private final int sessionId = RANDOM.nextInt();
private final Int2ObjectMap<ServerComputer> computers = new Int2ObjectOpenHashMap<>();
private int nextInstanceId;
@ -82,11 +81,10 @@ public class ServerComputerRegistry
computers.remove( instanceID );
}
public void reset()
void close()
{
for( ServerComputer computer : getComputers() ) computer.unload();
computers.clear();
sessionId = RANDOM.nextInt();
}
public Collection<ServerComputer> getComputers()

View File

@ -0,0 +1,183 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.computer.core;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.ComputerCraftAPIImpl;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.core.computer.GlobalEnvironment;
import dan200.computercraft.shared.CommonHooks;
import dan200.computercraft.shared.util.IDAssigner;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.World;
import net.minecraft.world.storage.FolderName;
import net.minecraftforge.versions.mcp.MCPVersion;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.Objects;
/**
* Stores ComputerCraft's server-side state for the lifetime of a {@link MinecraftServer}.
* <p>
* This is effectively a glorified singleton, holding references to most global state ComputerCraft stores
* (for instance, {@linkplain IDAssigner ID assignment} and {@linkplain ServerComputerRegistry running computers}. Its
* main purpose is to offer a single point of resetting the state ({@link ServerContext#close()} and ensure disciplined
* access to the current state, by ensuring callers have a {@link MinecraftServer} to hand.
*
* @see CommonHooks for where the context is created and torn down.
*/
public final class ServerContext
{
private static final FolderName FOLDER = new FolderName( ComputerCraft.MOD_ID );
private static @Nullable ServerContext instance;
private final MinecraftServer server;
private final ServerComputerRegistry registry = new ServerComputerRegistry();
private final GlobalEnvironment environment;
private final IDAssigner idAssigner;
private final Path storageDir;
private ServerContext( MinecraftServer server )
{
this.server = server;
storageDir = server.getWorldPath( FOLDER );
environment = new Environment( server );
idAssigner = new IDAssigner( storageDir.resolve( "ids.json" ) );
}
/**
* Start a new server context from the currently running Minecraft server.
*
* @param server The currently running Minecraft server.
* @throws IllegalStateException If a context is already present.
*/
public static void create( MinecraftServer server )
{
if( ServerContext.instance != null ) throw new IllegalStateException( "ServerContext already exists!" );
ServerContext.instance = new ServerContext( server );
}
/**
* Stops the current server context, resetting any state and terminating all computers.
*/
public static void close()
{
ServerContext instance = ServerContext.instance;
if( instance == null ) return;
instance.registry.close();
ServerContext.instance = null;
}
/**
* Get the {@link ServerContext} instance for the currently running Minecraft server.
*
* @param server The current server.
* @return The current server context.
*/
public static ServerContext get( MinecraftServer server )
{
Objects.requireNonNull( server, "Server cannot be null" );
ServerContext instance = ServerContext.instance;
if( instance == null ) throw new IllegalStateException( "ServerContext has not been started yet" );
if( instance.server != server )
{
throw new IllegalStateException( "Incorrect server given. Did ServerContext shutdown correctly?" );
}
return instance;
}
/**
* Get the current {@link GlobalEnvironment} computers should run under.
*
* @return The current {@link GlobalEnvironment}.
*/
GlobalEnvironment environment()
{
return environment;
}
/**
* Get the current {@link ServerComputerRegistry}.
*
* @return The global computer registry.
*/
public ServerComputerRegistry registry()
{
return registry;
}
/**
* Return the next available ID for a particular kind (for instance, a computer or particular peripheral type).
* <p>
* IDs are assigned incrementally, with the last assigned ID being stored in {@code ids.json} in our root
* {@linkplain #storageDir() storage folder}.
*
* @param kind The kind we're assigning an ID for, for instance {@code "computer"} or {@code "peripheral.monitor"}.
* @return The next available ID.
* @see ComputerCraftAPI#createUniqueNumberedSaveDir(World, String)
*/
public int getNextId( String kind )
{
return idAssigner.getNextId( kind );
}
/**
* Get the directory used for all ComputerCraft related information. This includes the computer/peripheral id store,
* and all computer data.
*
* @return The storge directory for ComputerCraft.
*/
public Path storageDir()
{
return storageDir;
}
private static final class Environment implements GlobalEnvironment
{
private final MinecraftServer server;
Environment( MinecraftServer server )
{
this.server = server;
}
@Override
public IMount createResourceMount( String domain, String subPath )
{
return ComputerCraftAPI.createResourceMount( domain, subPath );
}
@Override
public InputStream createResourceFile( String domain, String subPath )
{
return ComputerCraftAPIImpl.getResourceFile( server, domain, subPath );
}
@Nonnull
@Override
public String getHostString()
{
return String.format( "ComputerCraft %s (Minecraft %s)", ComputerCraftAPI.getInstalledVersion(), MCPVersion.getMCVersion() );
}
@Nonnull
@Override
public String getUserAgent()
{
return ComputerCraft.MOD_ID + "/" + ComputerCraftAPI.getInstalledVersion();
}
}
}

View File

@ -9,7 +9,7 @@ import dan200.computercraft.shared.Registry;
import dan200.computercraft.shared.computer.blocks.TileCommandComputer;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.core.ServerComputerRegistry;
import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.network.container.ComputerContainerData;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
@ -31,7 +31,7 @@ public class ContainerViewComputer extends ComputerMenuWithoutInventory
private static boolean canInteractWith( @Nonnull ServerComputer computer, @Nonnull PlayerEntity player )
{
// If this computer no longer exists then discard it.
if( ServerComputerRegistry.INSTANCE.get( computer.getInstanceID() ) != computer )
if( ServerContext.get( computer.getWorld().getServer() ).registry().get( computer.getInstanceID() ) != computer )
{
return false;
}

View File

@ -8,7 +8,7 @@ package dan200.computercraft.shared.peripheral.modem.wired;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.Peripherals;
import dan200.computercraft.shared.Registry;
import dan200.computercraft.shared.util.IDAssigner;
import dan200.computercraft.shared.computer.core.ServerContext;
import net.minecraft.block.Block;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.util.Direction;
@ -74,7 +74,7 @@ public final class WiredModemLocalPeripheral
else if( id < 0 || !type.equals( this.type ) )
{
this.type = type;
this.id = IDAssigner.getNextId( "peripheral." + type );
this.id = ServerContext.get( world.getServer() ).getNextId( "peripheral." + type );
}
return oldPeripheral == null || !oldPeripheral.equals( peripheral );

View File

@ -53,7 +53,7 @@ public class PocketComputerMenuProvider implements INamedContainerProvider
isTypingOnly ? Registry.ModContainers.POCKET_COMPUTER_NO_TERM.get() : Registry.ModContainers.POCKET_COMPUTER.get(), id, inventory,
p -> {
ItemStack stack = p.getItemInHand( hand );
return stack.getItem() == item && ItemPocketComputer.getServerComputer( stack ) == computer;
return stack.getItem() == item && ItemPocketComputer.getServerComputer( entity.level.getServer(), stack ) == computer;
},
computer, item.getFamily()
);

View File

@ -16,6 +16,7 @@ import dan200.computercraft.shared.PocketUpgrades;
import dan200.computercraft.shared.common.IColouredItem;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ServerComputerRegistry;
import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.computer.items.IComputerItem;
import dan200.computercraft.shared.network.container.ComputerContainerData;
import dan200.computercraft.shared.pocket.apis.PocketAPI;
@ -31,6 +32,7 @@ import net.minecraft.item.Item;
import net.minecraft.item.ItemGroup;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.ActionResult;
import net.minecraft.util.ActionResultType;
import net.minecraft.util.Hand;
@ -140,7 +142,7 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I
{
if( entity.level.isClientSide ) return false;
PocketServerComputer computer = getServerComputer( stack );
PocketServerComputer computer = getServerComputer( entity.level.getServer(), stack );
if( computer != null && tick( stack, entity.level, entity, computer ) ) entity.setItem( stack.copy() );
return false;
}
@ -228,7 +230,8 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I
int sessionID = getSessionID( stack );
PocketServerComputer computer = (PocketServerComputer) ServerComputerRegistry.INSTANCE.get( sessionID, getInstanceID( stack ) );
ServerComputerRegistry registry = ServerContext.get( world.getServer() ).registry();
PocketServerComputer computer = (PocketServerComputer) registry.get( sessionID, getInstanceID( stack ) );
if( computer == null )
{
int computerID = getComputerID( stack );
@ -241,7 +244,7 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I
computer = new PocketServerComputer( world, getComputerID( stack ), getLabel( stack ), getFamily() );
setInstanceID( stack, computer.register() );
setSessionID( stack, ServerComputerRegistry.INSTANCE.getSessionID() );
setSessionID( stack, registry.getSessionID() );
computer.updateValues( entity, stack, getUpgrade( stack ) );
computer.addAPI( new PocketAPI( computer ) );
@ -256,9 +259,9 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I
}
@Nullable
public static PocketServerComputer getServerComputer( @Nonnull ItemStack stack )
public static PocketServerComputer getServerComputer( MinecraftServer server, @Nonnull ItemStack stack )
{
return (PocketServerComputer) ServerComputerRegistry.INSTANCE.get( getSessionID( stack ), getInstanceID( stack ) );
return (PocketServerComputer) ServerContext.get( server ).registry().get( getSessionID( stack ), getInstanceID( stack ) );
}
// IComputerItem implementation

View File

@ -9,14 +9,11 @@ import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import dan200.computercraft.ComputerCraft;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.storage.FolderName;
import net.minecraftforge.fml.server.ServerLifecycleHooks;
import java.io.File;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.lang.ref.WeakReference;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
@ -28,61 +25,22 @@ public final class IDAssigner
{
public static final String COMPUTER = "computer";
private static final FolderName FOLDER = new FolderName( ComputerCraft.MOD_ID );
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
private static final Type ID_TOKEN = new TypeToken<Map<String, Integer>>()
{
}.getType();
private IDAssigner()
private final Path idFile;
private @Nullable Map<String, Integer> ids;
public IDAssigner( Path path )
{
idFile = path;
}
private static Map<String, Integer> ids;
private static WeakReference<MinecraftServer> server;
private static Path idFile;
public static File getDir()
public synchronized int getNextId( String kind )
{
return ServerLifecycleHooks.getCurrentServer().getWorldPath( FOLDER ).toFile();
}
private static boolean hasServerChanged()
{
if( server == null ) return true;
MinecraftServer currentServer = server.get();
return currentServer == null || currentServer != ServerLifecycleHooks.getCurrentServer();
}
public static synchronized int getNextId( String kind )
{
if( hasServerChanged() )
{
// The server has changed, refetch our ID map
server = new WeakReference<>( ServerLifecycleHooks.getCurrentServer() );
File dir = getDir();
dir.mkdirs();
// Load our ID file from disk
Map<String, Integer> newIds = null;
idFile = new File( dir, "ids.json" ).toPath();
if( Files.isRegularFile( idFile ) )
{
try( Reader reader = Files.newBufferedReader( idFile, StandardCharsets.UTF_8 ) )
{
newIds = GSON.fromJson( reader, ID_TOKEN );
}
catch( Exception e )
{
ComputerCraft.log.error( "Cannot load id file '" + idFile + "'", e );
}
}
if( newIds == null ) newIds = new HashMap<>();
ids = newIds;
}
if( ids == null ) ids = loadIds();
Integer existing = ids.get( kind );
int next = existing == null ? 0 : existing + 1;
@ -93,11 +51,39 @@ public final class IDAssigner
{
GSON.toJson( ids, writer );
}
catch( Exception e )
catch( IOException e )
{
ComputerCraft.log.error( "Cannot update ID file '" + idFile + "'", e );
ComputerCraft.log.error( "Cannot update ID file '{}'", idFile, e );
}
return next;
}
private Map<String, Integer> loadIds()
{
if( Files.isRegularFile( idFile ) )
{
try( Reader reader = Files.newBufferedReader( idFile, StandardCharsets.UTF_8 ) )
{
return GSON.fromJson( reader, ID_TOKEN );
}
catch( Exception e )
{
ComputerCraft.log.error( "Cannot load id file '" + idFile + "'", e );
}
}
else
{
try
{
Files.createDirectories( idFile.getParent() );
}
catch( IOException e )
{
ComputerCraft.log.error( "Cannot create owning directory, IDs will not be persisted", e );
}
}
return new HashMap<>();
}
}

View File

@ -112,7 +112,8 @@ public class ComputerTestDelegate
writer.write( "loadfile('test-rom/mcfly.lua', nil, _ENV)('test-rom/spec') cct_test.finish()" );
}
computer = new Computer( new BasicEnvironment( mount ), term, 0 );
BasicEnvironment environment = new BasicEnvironment( mount );
computer = new Computer( environment, environment, term, 0 );
computer.getEnvironment().setPeripheral( ComputerSide.TOP, new FakeModem() );
computer.addApi( new CctTestAPI() );

View File

@ -6,8 +6,9 @@ import dan200.computercraft.api.lua.MethodResult
import dan200.computercraft.api.peripheral.IPeripheral
import dan200.computercraft.api.peripheral.IWorkMonitor
import dan200.computercraft.core.computer.BasicEnvironment
import dan200.computercraft.core.computer.ComputerEnvironment
import dan200.computercraft.core.computer.ComputerSide
import dan200.computercraft.core.computer.IComputerEnvironment
import dan200.computercraft.core.computer.GlobalEnvironment
import dan200.computercraft.core.filesystem.FileSystem
import dan200.computercraft.core.terminal.Terminal
import dan200.computercraft.core.tracking.TrackingField
@ -23,7 +24,8 @@ abstract class NullApiEnvironment : IAPIEnvironment {
private val computerEnv = BasicEnvironment()
override fun getComputerID(): Int = 0
override fun getComputerEnvironment(): IComputerEnvironment = computerEnv
override fun getComputerEnvironment(): ComputerEnvironment = computerEnv
override fun getGlobalEnvironment(): GlobalEnvironment = computerEnv
override fun getMainThreadMonitor(): IWorkMonitor = throw IllegalStateException("Work monitor not available")
override fun getTerminal(): Terminal = throw IllegalStateException("Terminal not available")
override fun getFileSystem(): FileSystem = throw IllegalStateException("Terminal not available")

View File

@ -24,7 +24,7 @@ import java.net.URL;
/**
* A very basic environment.
*/
public class BasicEnvironment implements IComputerEnvironment
public class BasicEnvironment implements ComputerEnvironment, GlobalEnvironment
{
private final IWritableMount mount;

View File

@ -46,7 +46,8 @@ public class ComputerBootstrap
ComputerCraft.maxMainComputerTime = ComputerCraft.maxMainGlobalTime = Integer.MAX_VALUE;
Terminal term = new Terminal( ComputerCraft.computerTermWidth, ComputerCraft.computerTermHeight, true );
final Computer computer = new Computer( new BasicEnvironment( mount ), term, 0 );
BasicEnvironment environment = new BasicEnvironment( mount );
final Computer computer = new Computer( environment, environment, term, 0 );
AssertApi api = new AssertApi();
computer.addApi( api );

View File

@ -55,7 +55,8 @@ public class FakeComputerManager
@Nonnull
public static Computer create()
{
Computer computer = new Computer( new BasicEnvironment(), new Terminal( 51, 19, true ), 0 );
BasicEnvironment environment = new BasicEnvironment();
Computer computer = new Computer( environment, environment, new Terminal( 51, 19, true ), 0 );
machines.put( computer, new ConcurrentLinkedQueue<>() );
return computer;
}