diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index f3c0cc0cf..3c2f0c1d0 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -41,6 +41,7 @@ END TEMPLATE--> * If a sandbox error is caused by a compiler-generated method, the engine will now attempt to point out which using code is responsible. * Added `OrderedDictionary` and `System.StringComparer` to the sandbox whitelist. +* Added more overloads to `MapLoaderSystem` taking `TextReader`/`TextWriter` where appropriate. ### Bugfixes diff --git a/Robust.Shared/EntitySerialization/Systems/MapLoaderSystem.Load.cs b/Robust.Shared/EntitySerialization/Systems/MapLoaderSystem.Load.cs index 3c0828069..a2465fd29 100644 --- a/Robust.Shared/EntitySerialization/Systems/MapLoaderSystem.Load.cs +++ b/Robust.Shared/EntitySerialization/Systems/MapLoaderSystem.Load.cs @@ -19,8 +19,8 @@ namespace Robust.Shared.EntitySerialization.Systems; public sealed partial class MapLoaderSystem { /// - /// Tries to load entities from a yaml file. Whenever possible, you should try to use , - /// , or instead. + /// Tries to load entities from a YAML file. Whenever possible, you should try to use , + /// , or instead. /// public bool TryLoadGeneric( ResPath file, @@ -30,6 +30,7 @@ public sealed partial class MapLoaderSystem { grids = null; maps = null; + if (!TryLoadGeneric(file, out var data, options)) return false; @@ -39,33 +40,29 @@ public sealed partial class MapLoaderSystem } /// - /// Tries to load entities from a YAML file, taking in a raw byte stream. + /// Tries to load entities from a YAML text stream. Whenever possible, you should try to use , + /// , or instead. /// - /// The file contents to load from. - /// - /// The name of the file being loaded. This is used purely for logging/informational purposes. - /// - /// The result of the load operation. - /// Options for the load operation. - /// True if the load succeeded, false otherwise. - /// public bool TryLoadGeneric( - Stream file, - string fileName, - [NotNullWhen(true)] out LoadResult? result, + TextReader reader, + string source, + [NotNullWhen(true)] out HashSet>? maps, + [NotNullWhen(true)] out HashSet>? grids, MapLoadOptions? options = null) { - result = null; - - if (!TryReadFile(new StreamReader(file), out var data)) + grids = null; + maps = null; + if (!TryLoadGeneric(reader, source, out var data, options)) return false; - return TryLoadGeneric(data, fileName, out result, options); + maps = data.Maps; + grids = data.Grids; + return true; } /// - /// Tries to load entities from a yaml file. Whenever possible, you should try to use , - /// , or instead. + /// Tries to load entities from a YAML file. Whenever possible, you should try to use , + /// , or instead. /// /// The file to load. /// Data class containing information about the loaded entities @@ -80,9 +77,45 @@ public sealed partial class MapLoaderSystem return TryLoadGeneric(data, file.ToString(), out result, options); } - private bool TryLoadGeneric( + /// + /// Tries to load entities from a YAML text stream. Whenever possible, you should try to use , + /// , or instead. + /// + /// The text to load. + /// The name of the source, if any. This should be your file path (for example) + /// Data class containing information about the loaded entities + /// Optional Options for configuring loading behaviour. + public bool TryLoadGeneric(TextReader reader, string source, [NotNullWhen(true)] out LoadResult? result, MapLoadOptions? options = null) + { + result = null; + + if (!TryReadFile(reader, out var data)) + return false; + + return TryLoadGeneric(data, source, out result, options); + } + + /// + /// Tries to load entities from a YAML text stream. Whenever possible, you should try to use , + /// , or instead. + /// + /// The stream containing the text to load. + /// The name of the source, if any. This should be your file path (for example) + /// Data class containing information about the loaded entities + /// Optional Options for configuring loading behaviour. + public bool TryLoadGeneric(Stream stream, string source, [NotNullWhen(true)] out LoadResult? result, MapLoadOptions? options = null) + { + result = null; + + if (!TryReadFile(new StreamReader(stream, leaveOpen: true), out var data)) + return false; + + return TryLoadGeneric(data, source, out result, options); + } + + public bool TryLoadGeneric( MappingDataNode data, - string fileName, + string source, [NotNullWhen(true)] out LoadResult? result, MapLoadOptions? options = null) { @@ -118,7 +151,7 @@ public sealed partial class MapLoaderSystem if (!deserializer.TryProcessData()) { - Log.Debug($"Failed to process entity data in {fileName}"); + Log.Debug($"Failed to process entity data in {source}"); return false; } @@ -128,7 +161,7 @@ public sealed partial class MapLoaderSystem && deserializer.Result.Category != FileCategory.Unknown) { // Did someone try to load a map file as a grid or vice versa? - Log.Error($"Map {fileName} does not contain the expected data. Expected {expected} but got {deserializer.Result.Category}"); + Log.Error($"Map {source} does not contain the expected data. Expected {expected} but got {deserializer.Result.Category}"); Delete(deserializer.Result); return false; } @@ -139,7 +172,7 @@ public sealed partial class MapLoaderSystem } catch (Exception e) { - Log.Error($"Caught exception while creating entities for map {fileName}: {e}"); + Log.Error($"Caught exception while creating entities for map {source}: {e}"); Delete(deserializer.Result); throw; } @@ -149,7 +182,7 @@ public sealed partial class MapLoaderSystem if (opts.ExpectedCategory is { } exp && exp != deserializer.Result.Category) { // Did someone try to load a map file as a grid or vice versa? - Log.Error($"Map {fileName} does not contain the expected data. Expected {exp} but got {deserializer.Result.Category}"); + Log.Error($"Map {source} does not contain the expected data. Expected {exp} but got {deserializer.Result.Category}"); Delete(deserializer.Result); return false; } @@ -184,12 +217,33 @@ public sealed partial class MapLoaderSystem } /// - /// Tries to load a regular (non-map, non-grid) entity from a file. - /// The loaded entity will initially be in null-space. - /// If the file does not contain exactly one orphaned entity, this will return false and delete loaded entities. + /// Tries to load a regular (non-map, non-grid) entity from a YAML file. + /// The loaded entity will initially be in null-space. + /// If the file does not contain exactly one orphaned entity, this will return false and delete loaded entities. /// public bool TryLoadEntity( - ResPath path, + ResPath file, + [NotNullWhen(true)] out Entity? entity, + DeserializationOptions? options = null) + { + entity = null; + if (!TryGetReader(file, out var reader)) + return false; + + using (reader) + { + return TryLoadEntity(reader, file.ToString(), out entity, options); + } + } + + /// + /// Tries to load a regular (non-map, non-grid) entity from a YAML text stream. + /// The loaded entity will initially be in null-space. + /// If the file does not contain exactly one orphaned entity, this will return false and delete loaded entities. + /// + public bool TryLoadEntity( + TextReader reader, + string source, [NotNullWhen(true)] out Entity? entity, DeserializationOptions? options = null) { @@ -200,7 +254,7 @@ public sealed partial class MapLoaderSystem }; entity = null; - if (!TryLoadGeneric(path, out var result, opts)) + if (!TryLoadGeneric(reader, source, out var result, opts)) return false; if (result.Orphans.Count == 1) @@ -215,12 +269,35 @@ public sealed partial class MapLoaderSystem } /// - /// Tries to load a grid entity from a file and parent it to the given map. - /// If the file does not contain exactly one grid, this will return false and delete loaded entities. + /// Tries to load a grid entity from a YAML file and parent it to the given map. + /// If the file does not contain exactly one grid, this will return false and delete loaded entities. /// public bool TryLoadGrid( MapId map, - ResPath path, + ResPath file, + [NotNullWhen(true)] out Entity? grid, + DeserializationOptions? options = null, + Vector2 offset = default, + Angle rot = default) + { + grid = null; + if (!TryGetReader(file, out var reader)) + return false; + + using (reader) + { + return TryLoadGrid(map, reader, file.ToString(), out grid, options, offset, rot); + } + } + + /// + /// Tries to load a grid entity from a YAML text stream and parent it to the given map. + /// If the file does not contain exactly one grid, this will return false and delete loaded entities. + /// + public bool TryLoadGrid( + MapId map, + TextReader reader, + string source, [NotNullWhen(true)] out Entity? grid, DeserializationOptions? options = null, Vector2 offset = default, @@ -236,7 +313,7 @@ public sealed partial class MapLoaderSystem }; grid = null; - if (!TryLoadGeneric(path, out var result, opts)) + if (!TryLoadGeneric(reader, source, out var result, opts)) return false; if (result.Grids.Count == 1) @@ -250,11 +327,35 @@ public sealed partial class MapLoaderSystem } /// - /// Tries to load a grid entity from a file and parent it to a newly created map. + /// Tries to load a grid entity from a YAML file and parent it to a newly created map. /// If the file does not contain exactly one grid, this will return false and delete loaded entities. /// public bool TryLoadGrid( - ResPath path, + ResPath file, + [NotNullWhen(true)] out Entity? map, + [NotNullWhen(true)] out Entity? grid, + DeserializationOptions? options = null, + Vector2 offset = default, + Angle rot = default) + { + grid = null; + map = null; + if (!TryGetReader(file, out var reader)) + return false; + + using (reader) + { + return TryLoadGrid(reader, file.ToString(), out map, out grid, options, offset, rot); + } + } + + /// + /// Tries to load a grid entity from a YAML text stream and parent it to a newly created map. + /// If the file does not contain exactly one grid, this will return false and delete loaded entities. + /// + public bool TryLoadGrid( + TextReader reader, + string source, [NotNullWhen(true)] out Entity? map, [NotNullWhen(true)] out Entity? grid, DeserializationOptions? options = null, @@ -267,7 +368,7 @@ public sealed partial class MapLoaderSystem if (opts.PauseMaps) _mapSystem.SetPaused(mapUid, true); - if (!TryLoadGrid(mapId, path, out grid, options, offset, rot)) + if (!TryLoadGrid(mapId, reader, source, out grid, options, offset, rot)) { Del(mapUid); map = null; diff --git a/Robust.Shared/EntitySerialization/Systems/MapLoaderSystem.LoadMap.cs b/Robust.Shared/EntitySerialization/Systems/MapLoaderSystem.LoadMap.cs index 53a029d4e..aed100610 100644 --- a/Robust.Shared/EntitySerialization/Systems/MapLoaderSystem.LoadMap.cs +++ b/Robust.Shared/EntitySerialization/Systems/MapLoaderSystem.LoadMap.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Linq; using System.Numerics; using Robust.Shared.GameObjects; @@ -15,14 +16,41 @@ namespace Robust.Shared.EntitySerialization.Systems; public sealed partial class MapLoaderSystem { /// - /// Attempts to load a file containing a single map. - /// If the file does not contain exactly one map, this will return false and delete all loaded entities. + /// Attempts to load a YAML file containing a single map. + /// If the file does not contain exactly one map, this will return false and delete all loaded entities. /// /// - /// Note that this will not automatically initialize the map, unless specified via the . + /// Note that this will not automatically initialize the map, unless specified via the . /// public bool TryLoadMap( - ResPath path, + ResPath file, + [NotNullWhen(true)] out Entity? map, + [NotNullWhen(true)] out HashSet>? grids, + DeserializationOptions? options = null, + Vector2 offset = default, + Angle rot = default) + { + map = null; + grids = null; + if (!TryGetReader(file, out var reader)) + return false; + + using (reader) + { + return TryLoadMap(reader, file.ToString(), out map, out grids, options, offset, rot); + } + } + + /// + /// Attempts to load a YAML stream containing a single map. + /// If the file does not contain exactly one map, this will return false and delete all loaded entities. + /// + /// + /// Note that this will not automatically initialize the map, unless specified via the . + /// + public bool TryLoadMap( + TextReader reader, + string source, [NotNullWhen(true)] out Entity? map, [NotNullWhen(true)] out HashSet>? grids, DeserializationOptions? options = null, @@ -39,7 +67,7 @@ public sealed partial class MapLoaderSystem map = null; grids = null; - if (!TryLoadGeneric(path, out var result, opts)) + if (!TryLoadGeneric(reader, source, out var result, opts)) return false; if (result.Maps.Count == 1) @@ -54,17 +82,47 @@ public sealed partial class MapLoaderSystem } /// - /// Attempts to load a file containing a single map, assign it the given map id. + /// Attempts to load a YAML file containing a single map, assign it the given map id. /// /// - /// If possible, it is better to use which automatically assigns a . + /// If possible, it is better to use which automatically assigns a . /// /// - /// Note that this will not automatically initialize the map, unless specified via the . + /// Note that this will not automatically initialize the map, unless specified via the . /// public bool TryLoadMapWithId( MapId mapId, - ResPath path, + ResPath file, + [NotNullWhen(true)] out Entity? map, + [NotNullWhen(true)] out HashSet>? grids, + DeserializationOptions? options = null, + Vector2 offset = default, + Angle rot = default) + { + map = null; + grids = null; + if (!TryGetReader(file, out var reader)) + return false; + + using (reader) + { + return TryLoadMapWithId(mapId, reader, file.ToString(), out map, out grids, options, offset, rot); + } + } + + /// + /// Attempts to load a YAML text stream containing a single map, assign it the given map id. + /// + /// + /// If possible, it is better to use which automatically assigns a . + /// + /// + /// Note that this will not automatically initialize the map, unless specified via the . + /// + public bool TryLoadMapWithId( + MapId mapId, + TextReader reader, + string source, [NotNullWhen(true)] out Entity? map, [NotNullWhen(true)] out HashSet>? grids, DeserializationOptions? options = null, @@ -86,7 +144,7 @@ public sealed partial class MapLoaderSystem throw new Exception($"Target map already exists"); opts.ForceMapId = mapId; - if (!TryLoadGeneric(path, out var result, opts)) + if (!TryLoadGeneric(reader, source, out var result, opts)) return false; if (!_mapSystem.TryGetMap(mapId, out var uid) || !TryComp(uid, out MapComponent? comp)) @@ -98,12 +156,35 @@ public sealed partial class MapLoaderSystem } /// - /// Attempts to load a file containing a single map, and merge its children onto another map. After which the - /// loaded map gets deleted. + /// Attempts to load a YAML text stream containing a single map, and merge its children onto another map. After which + /// the loaded map gets deleted. /// public bool TryMergeMap( MapId mapId, - ResPath path, + ResPath file, + [NotNullWhen(true)] out HashSet>? grids, + DeserializationOptions? options = null, + Vector2 offset = default, + Angle rot = default) + { + grids = null; + if (!TryGetReader(file, out var reader)) + return false; + + using (reader) + { + return TryMergeMap(mapId, reader, file.ToString(), out grids, options, offset, rot); + } + } + + /// + /// Attempts to load a YAML file containing a single map, and merge its children onto another map. After which + /// the loaded map gets deleted. + /// + public bool TryMergeMap( + MapId mapId, + TextReader reader, + string source, [NotNullWhen(true)] out HashSet>? grids, DeserializationOptions? options = null, Vector2 offset = default, @@ -123,7 +204,7 @@ public sealed partial class MapLoaderSystem throw new Exception($"Target map {mapId} does not exist"); opts.MergeMap = mapId; - if (!TryLoadGeneric(path, out var result, opts)) + if (!TryLoadGeneric(reader, source, out var result, opts)) return false; if (!_mapSystem.TryGetMap(mapId, out var uid) || !TryComp(uid, out MapComponent? comp)) diff --git a/Robust.Shared/EntitySerialization/Systems/MapLoaderSystem.Save.cs b/Robust.Shared/EntitySerialization/Systems/MapLoaderSystem.Save.cs index c8ae5e7b7..74791fe22 100644 --- a/Robust.Shared/EntitySerialization/Systems/MapLoaderSystem.Save.cs +++ b/Robust.Shared/EntitySerialization/Systems/MapLoaderSystem.Save.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Linq; using Robust.Shared.GameObjects; using Robust.Shared.Map; @@ -59,10 +60,19 @@ public sealed partial class MapLoaderSystem } /// - /// Serialize a standard (non-grid, non-map) entity and all of its children and write the result to a - /// yaml file. + /// Serialize a standard (non-grid, non-map) entity and all of its children and write the result to a YAML file. /// - public bool TrySaveEntity(EntityUid entity, ResPath path, SerializationOptions? options = null) + public bool TrySaveEntity(EntityUid entity, ResPath target, SerializationOptions? options = null) + { + using var writer = GetWriterForPath(target); + return TrySaveEntity(entity, writer, options); + } + + /// + /// Serialize a standard (non-grid, non-map) entity and all of its children and write the result to a YAML text + /// stream. + /// + public bool TrySaveEntity(EntityUid entity, TextWriter target, SerializationOptions? options = null) { if (_mapQuery.HasComp(entity)) { @@ -97,12 +107,12 @@ public sealed partial class MapLoaderSystem return false; } - Write(path, data); + Write(target, data); return true; } /// - /// Serialize a map and all of its children and write the result to a yaml file. + /// Serialize a map and all of its children and write the result to a YAML file. /// public bool TrySaveMap(MapId mapId, ResPath path, SerializationOptions? options = null) { @@ -114,9 +124,18 @@ public sealed partial class MapLoaderSystem } /// - /// Serialize a map and all of its children and write the result to a yaml file. + /// Serialize a map and all of its children and write the result to a YAML file. /// - public bool TrySaveMap(EntityUid map, ResPath path, SerializationOptions? options = null) + public bool TrySaveMap(EntityUid map, ResPath target, SerializationOptions? options = null) + { + using var writer = GetWriterForPath(target); + return TrySaveMap(map, writer, options); + } + + /// + /// Serialize a map and all of its children and write the result to a YAML text stream. + /// + public bool TrySaveMap(EntityUid map, TextWriter target, SerializationOptions? options = null) { if (!_mapQuery.HasComp(map)) { @@ -145,14 +164,23 @@ public sealed partial class MapLoaderSystem return false; } - Write(path, data); + Write(target, data); return true; } /// - /// Serialize a grid and all of its children and write the result to a yaml file. + /// Serialize a grid and all of its children and write the result to a YAML file. /// - public bool TrySaveGrid(EntityUid grid, ResPath path, SerializationOptions? options = null) + public bool TrySaveGrid(EntityUid map, ResPath target, SerializationOptions? options = null) + { + using var writer = GetWriterForPath(target); + return TrySaveGrid(map, writer, options); + } + + /// + /// Serialize a grid and all of its children and write the result to a YAML text stream. + /// + public bool TrySaveGrid(EntityUid grid, TextWriter target, SerializationOptions? options = null) { if (!_gridQuery.HasComp(grid)) { @@ -187,32 +215,62 @@ public sealed partial class MapLoaderSystem return false; } - Write(path, data); + Write(target, data); return true; } /// - /// Serialize an entities and all of their children to a yaml file. - /// This makes no assumptions about the expected entity or resulting file category. - /// If possible, use the map/grid specific variants instead. + /// Serialize an entity and all of their children to a YAML file. + /// This makes no assumptions about the expected entity or resulting file category. + /// If possible, use the map/grid specific variants instead. /// public bool TrySaveGeneric( EntityUid uid, - ResPath path, + ResPath target, out FileCategory category, SerializationOptions? options = null) { - return TrySaveGeneric([uid], path, out category, options); + using var writer = GetWriterForPath(target); + return TrySaveGeneric(uid, writer, out category, options); } /// - /// Serialize one or more entities and all of their children to a yaml file. - /// This makes no assumptions about the expected entity or resulting file category. - /// If possible, use the map/grid specific variants instead. + /// Serialize an entity and all of their children to a YAML text stream. + /// This makes no assumptions about the expected entity or resulting file category. + /// If possible, use the map/grid specific variants instead. + /// + public bool TrySaveGeneric( + EntityUid uid, + TextWriter target, + out FileCategory category, + SerializationOptions? options = null) + { + return TrySaveGeneric([uid], target, out category, options); + } + + /// + /// Serialize one or more entities and all of their children to a YAML file. + /// This makes no assumptions about the expected entity or resulting file category. + /// If possible, use the map/grid specific variants instead. + /// + public bool TrySaveGeneric( + HashSet uid, + ResPath target, + out FileCategory category, + SerializationOptions? options = null) + { + using var writer = GetWriterForPath(target); + return TrySaveGeneric(uid, writer, out category, options); + } + + /// + /// Serialize one or more entities and all of their children to a YAML text stream. + /// This makes no assumptions about the expected entity or resulting file category. + /// If possible, use the map/grid specific variants instead. /// public bool TrySaveGeneric( HashSet entities, - ResPath path, + TextWriter target, out FileCategory category, SerializationOptions? options = null) { @@ -233,10 +291,21 @@ public sealed partial class MapLoaderSystem return false; } - Write(path, data); + Write(target, data); return true; } + /// + public bool TrySaveAllEntities(TextWriter target, SerializationOptions? options = null) + { + if (!TrySerializeAllEntities(out var data, options)) + return false; + + Write(target, data); + return true; + } + + /// public bool TrySaveAllEntities(ResPath path, SerializationOptions? options = null) { diff --git a/Robust.Shared/EntitySerialization/Systems/MapLoaderSystem.cs b/Robust.Shared/EntitySerialization/Systems/MapLoaderSystem.cs index 55077e374..59d581f88 100644 --- a/Robust.Shared/EntitySerialization/Systems/MapLoaderSystem.cs +++ b/Robust.Shared/EntitySerialization/Systems/MapLoaderSystem.cs @@ -42,17 +42,29 @@ public sealed partial class MapLoaderSystem : EntitySystem _gridQuery = GetEntityQuery(); } + private void Write(TextWriter target, MappingDataNode data) + { + var document = new YamlDocument(data.ToYaml()); + var stream = new YamlStream {document}; + stream.Save(new YamlMappingFix(new Emitter(target)), false); + } + + private StreamWriter GetWriterForPath(ResPath path) + { + Log.Info($"Saving serialized results to {path}"); + path = path.ToRootedPath(); + _resourceManager.UserData.CreateDir(path.Directory); + return _resourceManager.UserData.OpenWriteText(path); + } + private void Write(ResPath path, MappingDataNode data) { Log.Info($"Saving serialized results to {path}"); path = path.ToRootedPath(); - var document = new YamlDocument(data.ToYaml()); _resourceManager.UserData.CreateDir(path.Directory); using var writer = _resourceManager.UserData.OpenWriteText(path); - { - var stream = new YamlStream {document}; - stream.Save(new YamlMappingFix(new Emitter(writer)), false); - } + + Write(writer, data); } public bool TryReadFile(ResPath file, [NotNullWhen(true)] out MappingDataNode? data)