Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement world.GetConfig and SetConfig for environment variables #817

Merged
merged 10 commits into from
Sep 28, 2022
20 changes: 20 additions & 0 deletions Content.Tests/DMProject/Tests/Builtins/world_config.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

/proc/RunTest()
ASSERT(isnull(world.GetConfig("env")))

var/path = world.GetConfig("env", "PATH")
ASSERT(istext(path) && length(path))

var/env_var = "SUPER_SPECIAL_DM_ENVIRONMENT_KEY_FOR_TESTING"
var/env_value = "test value"
ASSERT(world.GetConfig("env", env_var) == null)
world.SetConfig("env", env_var, env_value)
var/retrieved = world.GetConfig("env", env_var)
ASSERT(retrieved == env_value)
ASSERT(retrieved != null)
world.SetConfig("env", env_var, null)
ASSERT(world.GetConfig("env", env_var) == null)

world.SetConfig("env", env_var, env_value)
world.SetConfig("env", env_var, 17)
ASSERT(world.GetConfig("env", env_var) == null)
55 changes: 31 additions & 24 deletions Content.Tests/DMTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,32 +60,38 @@ public void Cleanup(string compiledFile) {
[Test, TestCaseSource(nameof(GetTests))]
public void TestFiles(string sourceFile, DMTestFlags testFlags)
{
string compiledFile = Compile(sourceFile);
if (testFlags.HasFlag(DMTestFlags.CompileError)) {
Assert.IsNull(compiledFile, $"Expected an error during DM compilation");
Cleanup(compiledFile);
return;
}
string initialDirectory = Directory.GetCurrentDirectory();
try {
string compiledFile = Compile(Path.Join(initialDirectory, "Tests", sourceFile));
if (testFlags.HasFlag(DMTestFlags.CompileError)) {
Assert.IsNull(compiledFile, $"Expected an error during DM compilation");
Cleanup(compiledFile);
return;
}

Assert.IsTrue(compiledFile is not null && File.Exists(compiledFile), $"Failed to compile DM source file");
Assert.IsTrue(_dreamMan.LoadJson(compiledFile), $"Failed to load {compiledFile}");

(bool successfulRun, DreamValue returned, Exception? exception) = RunTest();
if (testFlags.HasFlag(DMTestFlags.RuntimeError)) {
Assert.IsFalse(successfulRun, "A DM runtime exception was expected");
} else {
if (exception != null)
Assert.IsTrue(successfulRun, $"A DM runtime exception was thrown: \"{exception.Message}\"");
else
Assert.IsTrue(successfulRun, "A DM runtime exception was thrown, and its message could not be recovered!");
}
Assert.IsTrue(compiledFile is not null && File.Exists(compiledFile), $"Failed to compile DM source file");
Assert.IsTrue(_dreamMan.LoadJson(compiledFile), $"Failed to load {compiledFile}");

if (testFlags.HasFlag(DMTestFlags.ReturnTrue)) {
returned.TryGetValueAsInteger(out int returnInt);
Assert.IsTrue(returnInt != 0, "Test was expected to return TRUE");
}
(bool successfulRun, DreamValue returned, Exception? exception) = RunTest();
if (testFlags.HasFlag(DMTestFlags.RuntimeError)) {
Assert.IsFalse(successfulRun, "A DM runtime exception was expected");
} else {
if (exception != null)
Assert.IsTrue(successfulRun, $"A DM runtime exception was thrown: \"{exception.Message}\"");
else
Assert.IsTrue(successfulRun, "A DM runtime exception was thrown, and its message could not be recovered!");
}

if (testFlags.HasFlag(DMTestFlags.ReturnTrue)) {
returned.TryGetValueAsInteger(out int returnInt);
Assert.IsTrue(returnInt != 0, "Test was expected to return TRUE");
}

Cleanup(compiledFile);
Cleanup(compiledFile);
} finally {
// Restore the original CurrentDirectory, since loading a compiled JSON changes it.
Directory.SetCurrentDirectory(initialDirectory);
}
}

private (bool Success, DreamValue Returned, Exception? except) RunTest() {
Expand All @@ -111,12 +117,13 @@ private static IEnumerable<object[]> GetTests()
Directory.SetCurrentDirectory(TestProject);

foreach (string sourceFile in Directory.GetFiles("Tests", "*.dm", SearchOption.AllDirectories)) {
string sourceFile2 = sourceFile.Substring("Tests/".Length);
DMTestFlags testFlags = GetDMTestFlags(sourceFile);
if (testFlags.HasFlag(DMTestFlags.Ignore))
continue;

yield return new object[] {
Path.GetFullPath(sourceFile),
sourceFile2,
testFlags
};
}
Expand Down
1 change: 1 addition & 0 deletions DMCompiler/DM/DMObjectTree.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ static DMObjectTree() {
public static void Reset() {
AllObjects.Clear();
AllProcs.Clear();
Globals.Clear();
GlobalProcs.Clear();
_pathToTypeId.Clear();
_dmObjectIdCounter = 0;
Expand Down
2 changes: 0 additions & 2 deletions DMCompiler/DMStandard/Types/World.dm
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,7 @@
proc/Profile(command, type, format)
set opendream_unimplemented = TRUE
proc/GetConfig(config_set,param)
set opendream_unimplemented = TRUE
proc/SetConfig(config_set,param,value)
set opendream_unimplemented = TRUE
proc/OpenPort(port)
set opendream_unimplemented = TRUE
proc/IsSubscribed(player, type)
Expand Down
3 changes: 2 additions & 1 deletion OpenDreamRuntime/DreamManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ partial class DreamManager : IDreamManager {
[Dependency] private readonly IDreamMapManager _dreamMapManager = default!;
[Dependency] private readonly IProcScheduler _procScheduler = default!;
[Dependency] private readonly DreamResourceManager _dreamResourceManager = default!;


public DreamObjectTree ObjectTree { get; private set; } = new();
public DreamObject WorldInstance { get; private set; }
Expand Down Expand Up @@ -92,6 +92,7 @@ public bool LoadJson(string? jsonPath)

if (_compiledJson.Globals != null) {
var jsonGlobals = _compiledJson.Globals;
Globals.Clear();
Globals.EnsureCapacity(jsonGlobals.GlobalCount);

for (int i = 0; i < jsonGlobals.GlobalCount; i++) {
Expand Down
9 changes: 6 additions & 3 deletions OpenDreamRuntime/Procs/DMOpcodeHandlers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1828,10 +1828,13 @@ private static DreamValue MultiplyValues(DreamValue first, DreamValue second) {
}

private static DreamValue DivideValues(DreamValue first, DreamValue second) {
if (first.Value == null) {
if (first == DreamValue.Null) {
return new(0);
} else if (first.Type == DreamValue.DreamValueType.Float && second.Type == DreamValue.DreamValueType.Float) {
return new(first.GetValueAsFloat() / second.GetValueAsFloat());
} else if (first.TryGetValueAsFloat(out var firstFloat) && second.TryGetValueAsFloat(out var secondFloat)) {
if (secondFloat == 0) {
throw new Exception("Division by zero");
}
return new(firstFloat / secondFloat);
} else {
throw new Exception("Invalid divide operation on " + first + " and " + second);
}
Expand Down
2 changes: 1 addition & 1 deletion OpenDreamRuntime/Procs/DreamProcAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public DreamProcAttribute(string name) {
sealed class DreamProcParameterAttribute : Attribute {
public string Name;
public DreamValue.DreamValueType Type;
public object DefaultValue;
public object? DefaultValue;

public DreamProcParameterAttribute(string name) {
Name = name;
Expand Down
2 changes: 2 additions & 0 deletions OpenDreamRuntime/Procs/Native/DreamProcNative.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ public static void SetupNativeProcs(DreamObjectTree objectTree) {

DreamObjectDefinition world = objectTree.GetObjectDefinition(DreamPath.World);
world.SetNativeProc(DreamProcNativeWorld.NativeProc_Export);
world.SetNativeProc(DreamProcNativeWorld.NativeProc_GetConfig);
world.SetNativeProc(DreamProcNativeWorld.NativeProc_SetConfig);
}
}
}
60 changes: 58 additions & 2 deletions OpenDreamRuntime/Procs/Native/DreamProcNativeWorld.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ internal static class DreamProcNativeWorld
{
[DreamProc("Export")]
[DreamProcParameter("Addr", Type = DreamValue.DreamValueType.String)]
[DreamProcParameter("File", Type = DreamValue.DreamValueType.DreamObject, DefaultValue = null)]
[DreamProcParameter("File", Type = DreamValue.DreamValueType.DreamObject)]
[DreamProcParameter("Persist", Type = DreamValue.DreamValueType.Float, DefaultValue = 0)]
[DreamProcParameter("Clients", Type = DreamValue.DreamValueType.DreamObject, DefaultValue = null)]
[DreamProcParameter("Clients", Type = DreamValue.DreamValueType.DreamObject)]
public static async Task<DreamValue> NativeProc_Export(AsyncNativeProc.State state)
{
var addr = state.Arguments.GetArgument(0, "Addr").Stringify();
Expand All @@ -38,5 +38,61 @@ public static async Task<DreamValue> NativeProc_Export(AsyncNativeProc.State sta

return new DreamValue(list);
}

[DreamProc("GetConfig")]
[DreamProcParameter("config_set", Type = DreamValue.DreamValueType.String)]
[DreamProcParameter("param", Type = DreamValue.DreamValueType.String)]
public static DreamValue NativeProc_GetConfig(DreamObject src, DreamObject usr, DreamProcArguments arguments)
{
arguments.GetArgument(0, "config_set").TryGetValueAsString(out string config_set);
var param = arguments.GetArgument(1, "param");

switch (config_set) {
case "env":
if (param == DreamValue.Null) {
// DM ref says: "If no parameter is specified, a list of the names of all available parameters is returned."
// but apparently it's actually just null for "env".
return DreamValue.Null;
} else if (param.TryGetValueAsString(out string paramString) && Environment.GetEnvironmentVariable(paramString) is string strValue) {
return new DreamValue(strValue);
} else {
return DreamValue.Null;
}
case "admin":
throw new NotSupportedException("Unsupported GetConfig config_set: " + config_set);
case "ban":
case "keyban":
case "ipban":
throw new NotSupportedException("Unsupported GetConfig config_set: " + config_set);
default:
throw new ArgumentException("Incorrect GetConfig config_set: " + config_set);
}
}

[DreamProc("SetConfig")]
[DreamProcParameter("config_set", Type = DreamValue.DreamValueType.String)]
[DreamProcParameter("param", Type = DreamValue.DreamValueType.String)]
[DreamProcParameter("value", Type = DreamValue.DreamValueType.String)]
public static DreamValue NativeProc_SetConfig(DreamObject src, DreamObject usr, DreamProcArguments arguments)
{
arguments.GetArgument(0, "config_set").TryGetValueAsString(out string config_set);
arguments.GetArgument(1, "param").TryGetValueAsString(out string param);
var value = arguments.GetArgument(2, "value");

switch (config_set) {
case "env":
value.TryGetValueAsString(out string valueString);
Environment.SetEnvironmentVariable(param, valueString);
return DreamValue.Null;
case "admin":
throw new NotSupportedException("Unsupported SetConfig config_set: " + config_set);
case "ban":
case "keyban":
case "ipban":
throw new NotSupportedException("Unsupported SetConfig config_set: " + config_set);
default:
throw new ArgumentException("Incorrect SetConfig config_set: " + config_set);
}
}
}
}
1 change: 1 addition & 0 deletions OpenDreamRuntime/Resources/DreamResourceManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public void Initialize(string jsonPath)

public void SetDirectory(string directory) {
RootPath = directory;
// Used to ensure external DLL calls see a consistent current directory.
Directory.SetCurrentDirectory(RootPath);

Logger.DebugS("opendream.res", $"Resource root path set to {RootPath}");
Expand Down