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

Add support for tmp savefiles, fix multiple handles for savefiles, clean up #1535

Merged
merged 6 commits into from
Dec 13, 2023
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 OpenDreamRuntime/DreamManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public void Update() {
_procScheduler.Process();
UpdateStat();
_dreamMapManager.UpdateTiles();

DreamObjectSavefile.FlushAllUpdates();
WorldInstance.SetVariableValue("cpu", WorldInstance.GetVariable("tick_usage"));
}

Expand Down
4 changes: 2 additions & 2 deletions OpenDreamRuntime/EntryPoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ public override void PostInit() {

protected override void Dispose(bool disposing) {
// Write every savefile to disk
foreach (var savefile in DreamObjectSavefile.Savefiles) {
savefile.Flush();
foreach (var savefile in DreamObjectSavefile.Savefiles.ToArray()) { //ToArray() to avoid modifying the collection while iterating over it
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copying the list seems unnecessary, but it's probably fine if it doesn't grow too big (especially once softdels are added)

savefile.Close();
amylizzle marked this conversation as resolved.
Show resolved Hide resolved
}

_dreamManager.Shutdown();
Expand Down
81 changes: 67 additions & 14 deletions OpenDreamRuntime/Objects/Types/DreamObjectSavefile.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.Json;
using System.IO;
using System.Text.Json;
using OpenDreamRuntime.Procs;
using OpenDreamRuntime.Resources;
using OpenDreamShared.Dream;
Expand All @@ -9,42 +10,74 @@ public sealed class DreamObjectSavefile : DreamObject {
public sealed class SavefileDirectory : Dictionary<string, DreamValue> { }

public static readonly List<DreamObjectSavefile> Savefiles = new();
//basically a global database of savefile contents, which each savefile datum points to - this preserves state between savefiles and reduces memory usage
private static readonly Dictionary<string, Dictionary<string, SavefileDirectory>> SavefileDirectories = new();
private static readonly HashSet<DreamObjectSavefile> _savefilesToFlush = new();

public override bool ShouldCallNew => false;

public DreamResource Resource;
public Dictionary<string, SavefileDirectory> Directories;
public Dictionary<string, SavefileDirectory> Directories => SavefileDirectories[Resource.ResourcePath ?? ""];
public SavefileDirectory CurrentDir => Directories[_currentDirPath];

private string _currentDirPath = "/";

//Temporary savefiles should be deleted when the DreamObjectSavefile is deleted. Temporary savefiles can be created by creating a new savefile datum with a null filename or an entry in the world's resource cache
private bool _isTemporary = false;

private static ISawmill? _sawmill = null;
/// <summary>
/// Flushes all savefiles that have been marked as needing flushing. Basically just used to call Flush() between ticks instead of on every write.
/// </summary>
public static void FlushAllUpdates() {
_sawmill ??= Logger.GetSawmill("opendream.res");
foreach (DreamObjectSavefile savefile in _savefilesToFlush) {
try {
savefile.Flush();
} catch (Exception e) {
_sawmill.Error($"Error flushing savefile {savefile.Resource.ResourcePath}: {e}");
}
}
_savefilesToFlush.Clear();
}

public DreamObjectSavefile(DreamObjectDefinition objectDefinition) : base(objectDefinition) {

}

public override void Initialize(DreamProcArguments args) {
base.Initialize(args);

string filename = args.GetArgument(0).GetValueAsString();
args.GetArgument(0).TryGetValueAsString(out string? filename);
DreamValue timeout = args.GetArgument(1); //TODO: timeout

if (string.IsNullOrEmpty(filename)) {
_isTemporary = true;
filename = Path.GetTempPath() + "tmp_opendream_savefile_" + System.DateTime.Now.Ticks.ToString();
}

Resource = DreamResourceManager.LoadResource(filename);

string? data = Resource.ReadAsString();
if (!string.IsNullOrEmpty(data)) {
Directories = JsonSerializer.Deserialize<Dictionary<string, SavefileDirectory>>(data);
} else {
Directories = new() {
{ "/", new SavefileDirectory() }
};
if(!SavefileDirectories.ContainsKey(filename)) {
//if the savefile hasn't already been loaded, load it or create it
string? data = Resource.ReadAsString();

if (!string.IsNullOrEmpty(data)) {
SavefileDirectories.Add(filename, JsonSerializer.Deserialize<Dictionary<string, SavefileDirectory>>(data));
} else {
SavefileDirectories.Add(filename, new() {
{ "/", new SavefileDirectory() }
});
//create the file immediately
Flush();
}
}

Savefiles.Add(this);
}

protected override void HandleDeletion() {
Savefiles.Remove(this);

Close();
base.HandleDeletion();
}

Expand All @@ -53,6 +86,25 @@ public void Flush() {
Resource.Output(new DreamValue(JsonSerializer.Serialize(Directories)));
}

public void Close() {
Flush();
if (_isTemporary && Resource.ResourcePath != null) {
File.Delete(Resource.ResourcePath);
}
//check to see if the file is still in use by another savefile datum
if(Resource.ResourcePath != null) {
bool fineToDelete = true;
foreach (var savefile in Savefiles)
if (savefile != this && savefile.Resource.ResourcePath == Resource.ResourcePath) {
fineToDelete = false;
break;
}
if (fineToDelete)
SavefileDirectories.Remove(Resource.ResourcePath);
}
Savefiles.Remove(this);
}

protected override bool TryGetVar(string varName, out DreamValue value) {
switch (varName) {
case "cd":
Expand Down Expand Up @@ -102,7 +154,7 @@ public override DreamValue OperatorIndex(DreamValue index) {
throw new Exception($"Invalid savefile index {index}");

if (CurrentDir.TryGetValue(entryName, out DreamValue entry)) {
return entry;
return entry; //TODO: This should be something like value.DMProc("Read", new DreamProcArguments(this)) for DreamObjects and a copy for everything else
} else {
return DreamValue.Null;
}
Expand All @@ -112,7 +164,8 @@ public override void OperatorIndexAssign(DreamValue index, DreamValue value) {
if (!index.TryGetValueAsString(out string? entryName))
throw new Exception($"Invalid savefile index {index}");

CurrentDir[entryName] = value;
CurrentDir[entryName] = value; //TODO: This should be something like value.DMProc("Write", new DreamProcArguments(this)) for DreamObjects and a copy for everything else
_savefilesToFlush.Add(this); //mark this as needing flushing
}

private void ChangeDirectory(string path) {
Expand Down
Loading