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

[DNM] implement datum saving #1546

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from 4 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
43 changes: 43 additions & 0 deletions Content.Tests/DMProject/Tests/Savefile/datum_saving.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/datum/foo
var/best_map = "pl_upward" // mutated by RunTest(), should save
var/worst_map = "pl_badwater" // same as, should not be saved
var/null_me = "ok" // should save as null

var/tmp/current_map = "yeah" // tmp, should not save
var/const/default_cube = "delete it" // const, should not save

/datum/foo/Write(savefile/F)
. = ..(F)
ASSERT(F["type"] == /datum/foo)
ASSERT(F["current_map"] == null)
ASSERT(F["default_cube"] == null)

/proc/RunTest()
var/savefile/S = new("delme.sav")

var/datum/foo/F = new()
F.best_map = "pl_pier"
F.null_me = null

S["mapdata"] << F

// test the savefile's contents
ASSERT(S["mapdata/.0/type"] == /datum/foo)
ASSERT(S["mapdata/.0/best_map"] == "pl_pier")
ASSERT(S["mapdata/.0/null_me"] == null)
ASSERT(S["mapdata/.0/worst_map"] == null)
ASSERT(S["mapdata/.0/current_map"] == null)
ASSERT(S["mapdata/.0/default_cube"] == null)

var/datum/foo/W = new()
S["mapdata"] >> W

// load test
ASSERT(istype(W))
ASSERT(W.best_map == "pl_pier")
ASSERT(W.worst_map == null)
ASSERT(W.null_me == null)

fdel("delme.sav")
return TRUE

2 changes: 0 additions & 2 deletions DMCompiler/DMStandard/Types/Datum.dm
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,5 @@
proc/Topic(href, href_list)

proc/Read(savefile/F)
set opendream_unimplemented = TRUE

proc/Write(savefile/F)
set opendream_unimplemented = TRUE
37 changes: 26 additions & 11 deletions OpenDreamRuntime/Objects/Types/DreamObjectSavefile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
namespace OpenDreamRuntime.Objects.Types;

public sealed class DreamObjectSavefile : DreamObject {
public sealed class SavefileDirectory : Dictionary<string, DreamValue> { }

public sealed class SavefileDirectory : Dictionary<string, DreamValue> {
}

public static readonly List<DreamObjectSavefile> Savefiles = new();

Expand Down Expand Up @@ -98,28 +100,41 @@ protected override void SetVar(string varName, DreamValue value) {
}

public override DreamValue OperatorIndex(DreamValue index) {
if (!index.TryGetValueAsString(out string? entryName))
if (!index.TryGetValueAsString(out var entryName))
throw new Exception($"Invalid savefile index {index}");

if (CurrentDir.TryGetValue(entryName, out DreamValue entry)) {
return entry;
} else {
return DreamValue.Null;
return CurrentDir.TryGetValue(entryName, out DreamValue entry) ? entry : DreamValue.Null;
}

/// <summary>
/// Add or assign value on the targeted dir (or on the current dir)
/// </summary>
/// <param name="index">Index or empty for current dir</param>
/// <param name="value"></param>
public void AddOrAssignValue(string? index, DreamValue value) {
if (value.TryGetValueAsDreamObject(out var ceralizableObject)) {
// pcall here
return;
}
// no idx means its a << op TODO replace the original (idk how byond knows what is the original)
CurrentDir[index ?? $".{CurrentDir.Count-1}"] = value;
}

public override void OperatorIndexAssign(DreamValue index, DreamValue value) {
if (!index.TryGetValueAsString(out string? entryName))
if (!index.TryGetValueAsString(out var entryName))
throw new Exception($"Invalid savefile index {index}");

CurrentDir[entryName] = value;
AddOrAssignValue(entryName, value);
}

// << statement
public override void OperatorOutput(DreamValue value) {
AddOrAssignValue(null, value);
}

private void ChangeDirectory(string path) {
_currentDirPath = new DreamPath(_currentDirPath).AddToPath(path).PathString;

if (!Directories.ContainsKey(_currentDirPath)) {
Directories.Add(_currentDirPath, new SavefileDirectory());
}
Directories.TryAdd(_currentDirPath, new SavefileDirectory());
}
}
3 changes: 3 additions & 0 deletions OpenDreamRuntime/Procs/Native/DreamProcNative.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ public static void SetupNativeProcs(DreamObjectTree objectTree) {
objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_winget);
objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_winset);

objectTree.SetNativeProc(objectTree.Datum, DreamProcNativeDatum.NativeProc_Read);
objectTree.SetNativeProc(objectTree.Datum, DreamProcNativeDatum.NativeProc_Write);

objectTree.SetNativeProc(objectTree.List, DreamProcNativeList.NativeProc_Add);
objectTree.SetNativeProc(objectTree.List, DreamProcNativeList.NativeProc_Copy);
objectTree.SetNativeProc(objectTree.List, DreamProcNativeList.NativeProc_Cut);
Expand Down
45 changes: 45 additions & 0 deletions OpenDreamRuntime/Procs/Native/DreamProcNativeDatum.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using OpenDreamRuntime.Objects;
using OpenDreamRuntime.Objects.Types;

namespace OpenDreamRuntime.Procs.Native;

internal static class DreamProcNativeDatum {
[DreamProc("Write")]
[DreamProcParameter("F", Type = DreamValue.DreamValueTypeFlag.DreamObject)]
public static DreamValue NativeProc_Write(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) {
var probablySavefile = bundle.GetArgument(0, "F");
if (!probablySavefile.TryGetValueAsDreamObject(out DreamObjectSavefile? savefile) && savefile == null)
return DreamValue.Null; // error out bad path or something

savefile.OperatorIndexAssign(new DreamValue("type"), src!.GetVariable("type"));

foreach (var key in src.GetVariableNames()) {
if (!src.IsSaved(key)) continue;
var result = src.GetVariable(key);

// skip if initial var is same
if (src.ObjectDefinition.TryGetVariable(key, out var val) && val == result) continue;
savefile.OperatorIndexAssign(new DreamValue(key), result);
}
return DreamValue.Null;
}

[DreamProc("Read")]
[DreamProcParameter("F", Type = DreamValue.DreamValueTypeFlag.DreamObject)]
public static DreamValue NativeProc_Read(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) {
var probablySavefile = bundle.GetArgument(0, "F");
if (!probablySavefile.TryGetValueAsDreamObject(out DreamObjectSavefile? savefile) && savefile == null)
return DreamValue.Null; // TODO what err is appropriate here?

foreach (var key in src!.GetVariableNames()) {
if (!src.IsSaved(key)) continue;
if (!savefile.CurrentDir.TryGetValue(key, out DreamValue result)) {
src.SetVariableValue(key, DreamValue.Null); // blame byond not skipping keys and instead nulling intentionally
continue;
}
src.SetVariableValue(key, result);
}
return DreamValue.Null;
}

}