Skip to content

Commit c3dfe42

Browse files
committed
small improvements plus fix tests
1 parent a026367 commit c3dfe42

9 files changed

Lines changed: 98 additions & 69 deletions

File tree

AttributeList.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,14 +249,27 @@ public virtual object this[string name]
249249
{
250250
PropertyInfo prop = GetType().GetProperty(prop_attr.Name, BindingFlags.Public | BindingFlags.Instance);
251251

252-
if (null != prop && prop.CanWrite)
252+
if (prop != null && prop.CanWrite)
253253
{
254+
// were actually fine with this being null, it will just set the value to null
255+
// but need to check so the type check doesn't fail if it is null
256+
if(value != null)
257+
{
258+
var valueType = value.GetType();
259+
if (prop.PropertyType != valueType)
260+
{
261+
throw new InvalidDataException($"class property '{prop.Name}' with type '{prop.PropertyType}' does not match the type '{valueType}' of the value being set, this is likely a mismatch between the real class and the class from the datamodel");
262+
}
263+
}
264+
254265
prop.SetValue(this, value);
255266
}
256267
else
257268
{
258269
throw new InvalidDataException("Property of deserialisation class must be writeable, make sure it's public and has a public setter");
259270
}
271+
272+
return;
260273
}
261274

262275
Attribute old_attr;

Codecs/Binary.cs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -346,14 +346,9 @@ object ReadValue(Datamodel dm, Type type, bool raw_string)
346346
throw new ArgumentException(type == null ? "No type provided to GetValue()" : "Cannot read value of type " + type.Name);
347347
}
348348

349-
public Datamodel Decode(string encoding, int encoding_version, string format, int format_version, Stream stream, DeferredMode defer_mode, Assembly callingAssembly, bool attemptReflection)
349+
public Datamodel Decode(string encoding, int encoding_version, string format, int format_version, Stream stream, DeferredMode defer_mode, ReflectionParams reflectionParams)
350350
{
351-
Dictionary<string, Type> callingTypes = new();
352-
353-
foreach (var classType in callingAssembly.DefinedTypes)
354-
{
355-
callingTypes.Add(classType.Name, classType);
356-
}
351+
var types = CodecUtilities.GetReflectionTypes(reflectionParams);
357352

358353
stream.Seek(0, SeekOrigin.Begin);
359354
while (true)
@@ -393,9 +388,9 @@ public Datamodel Decode(string encoding, int encoding_version, string format, in
393388
var id = new Guid(BitConverter.IsLittleEndian ? id_bits : id_bits.Reverse().ToArray());
394389

395390
Element? elem = null;
396-
var matchedType = callingTypes.TryGetValue(type, out var classType);
391+
var matchedType = types.TryGetValue(type, out var classType);
397392

398-
if(matchedType)
393+
if(matchedType && reflectionParams.AttemptReflection)
399394
{
400395
var isElementDerived = IsElementDerived(classType);
401396
if (isElementDerived && classType.Name == type)
@@ -437,7 +432,7 @@ public Datamodel Decode(string encoding, int encoding_version, string format, in
437432
foreach (var i in Enumerable.Range(0, num_attrs))
438433
{
439434
var name = StringDict.ReadString();
440-
if (defer_mode == DeferredMode.Automatic && attemptReflection == false)
435+
if (defer_mode == DeferredMode.Automatic && reflectionParams.AttemptReflection == false)
441436
{
442437
CodecUtilities.AddDeferredAttribute(elem, name, Reader.BaseStream.Position);
443438
SkipAttribute();

Codecs/KeyValues2.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -598,7 +598,7 @@ object Decode_ParseValue(Type type, string value)
598598
else throw new ArgumentException($"Internal error: ParseValue passed unsupported type: {type}.");
599599
}
600600

601-
public Datamodel Decode(string encoding, int encoding_version, string format, int format_version, Stream stream, DeferredMode defer_mode, Assembly callingAssembly, bool attemptReflection)
601+
public Datamodel Decode(string encoding, int encoding_version, string format, int format_version, Stream stream, DeferredMode defer_mode, ReflectionParams reflectionParams)
602602
{
603603
DM = new Datamodel(format, format_version);
604604

Datamodel.cs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -232,32 +232,32 @@ public void Save(string path, string encoding, int encoding_version)
232232
/// </summary>
233233
/// <param name="stream">The input Stream.</param>
234234
/// <param name="defer_mode">How to handle deferred loading.</param>
235-
public static Datamodel Load(Stream stream, DeferredMode defer_mode = DeferredMode.Automatic, bool attemptReflection = false)
235+
public static Datamodel Load(Stream stream, DeferredMode defer_mode = DeferredMode.Automatic, ReflectionParams? reflectionParams = null)
236236
{
237-
return Load_Internal(stream, Assembly.GetCallingAssembly(), defer_mode, attemptReflection);
237+
return Load_Internal(stream, Assembly.GetCallingAssembly(), defer_mode, reflectionParams);
238238
}
239239
/// <summary>
240240
/// Loads a Datamodel from a byte array.
241241
/// </summary>
242242
/// <param name="stream">The input Stream.</param>
243243
/// <param name="defer_mode">How to handle deferred loading.</param>
244-
public static Datamodel Load(byte[] data, DeferredMode defer_mode = DeferredMode.Automatic, bool attemptReflection = false)
244+
public static Datamodel Load(byte[] data, DeferredMode defer_mode = DeferredMode.Automatic, ReflectionParams? reflectionParams = null)
245245
{
246-
return Load_Internal(new MemoryStream(data, true), Assembly.GetCallingAssembly(), defer_mode, attemptReflection);
246+
return Load_Internal(new MemoryStream(data, true), Assembly.GetCallingAssembly(), defer_mode, reflectionParams);
247247
}
248248

249249
/// <summary>
250250
/// Loads a Datamodel from a file path.
251251
/// </summary>
252252
/// <param name="path">The source file path.</param>
253253
/// <param name="defer_mode">How to handle deferred loading.</param>
254-
public static Datamodel Load(string path, DeferredMode defer_mode = DeferredMode.Automatic, bool attemptReflection = false)
254+
public static Datamodel Load(string path, DeferredMode defer_mode = DeferredMode.Automatic, ReflectionParams? reflectionParams = null)
255255
{
256256
var stream = File.OpenRead(path);
257257
Datamodel dm = null;
258258
try
259259
{
260-
dm = Load_Internal(stream, Assembly.GetCallingAssembly(), defer_mode, attemptReflection);
260+
dm = Load_Internal(stream, Assembly.GetCallingAssembly(), defer_mode, reflectionParams);
261261
return dm;
262262
}
263263
finally
@@ -266,8 +266,11 @@ public static Datamodel Load(string path, DeferredMode defer_mode = DeferredMode
266266
}
267267
}
268268

269-
private static Datamodel Load_Internal(Stream stream, Assembly callingAssembly, DeferredMode defer_mode = DeferredMode.Automatic, bool attemptReflection = false)
269+
private static Datamodel Load_Internal(Stream stream, Assembly callingAssembly, DeferredMode defer_mode = DeferredMode.Automatic, ReflectionParams? reflectionParams = null)
270270
{
271+
reflectionParams ??= new();
272+
reflectionParams.AssembliesToSearch.Add(callingAssembly);
273+
271274
stream.Seek(0, SeekOrigin.Begin);
272275
var header = string.Empty;
273276
int b;
@@ -293,7 +296,7 @@ private static Datamodel Load_Internal(Stream stream, Assembly callingAssembly,
293296

294297
ICodec codec = GetCodec(encoding, encoding_version);
295298

296-
var dm = codec.Decode(encoding, encoding_version, format, format_version, stream, defer_mode, callingAssembly, attemptReflection);
299+
var dm = codec.Decode(encoding, encoding_version, format, format_version, stream, defer_mode, reflectionParams);
297300
if (defer_mode == DeferredMode.Automatic && codec is IDeferredAttributeCodec deferredCodec)
298301
{
299302
dm.Stream = stream;

ICodec.cs

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.IO;
44
using System.Numerics;
55
using System.Reflection;
6+
using System.Collections.Generic;
67

78
namespace Datamodel.Codecs
89
{
@@ -30,9 +31,31 @@ public interface ICodec
3031
/// <param name="stream">The input stream. Its position will always be 0. Do not dispose.</param>
3132
/// <param name="defer_mode">The deferred loading mode specified by the caller. Only relevant to implementers of <see cref="IDeferredAttributeCodec"/></param>
3233
/// <returns></returns>
33-
Datamodel Decode(string encoding, int encoding_version, string format, int format_version, Stream stream, DeferredMode defer_mode, Assembly callingAssembly, bool attemptReflection);
34+
Datamodel Decode(string encoding, int encoding_version, string format, int format_version, Stream stream, DeferredMode defer_mode, ReflectionParams reflectionParams);
3435
}
3536

37+
/// <summary>
38+
/// Parameters for reflection based deserialisation
39+
/// By default it will look for types in the calling assembly (the one which made this class)
40+
/// </summary>
41+
public class ReflectionParams
42+
{
43+
public bool AttemptReflection;
44+
public List<Type> AdditionalTypes;
45+
public List<Assembly> AssembliesToSearch;
46+
47+
/// <param name="attemptReflection">If to use reflection or not.</param>
48+
/// <param name="additionalTypes">Additional types to consider when matching.</param>
49+
/// <param name="assembliesToSearch">Additional assemblies to look for types in.</param>
50+
public ReflectionParams(bool attemptReflection = true, List<Type>? additionalTypes = null, List<Assembly>? assembliesToSearch = null)
51+
{
52+
AttemptReflection = attemptReflection;
53+
AdditionalTypes = additionalTypes ??= new();
54+
AssembliesToSearch = assembliesToSearch ??= new();
55+
}
56+
}
57+
58+
3659
/// <summary>
3760
/// Defines methods for the deferred loading of <see cref="Attribute"/> values.
3861
/// </summary>
@@ -171,6 +194,29 @@ public static void AddDeferredAttribute(Element elem, string key, long offset)
171194
if (offset <= 0) throw new ArgumentOutOfRangeException(nameof(offset), "Address must be greater than 0.");
172195
elem.Add(key, offset);
173196
}
197+
198+
public static Dictionary<string, Type> GetReflectionTypes(ReflectionParams reflectionParams)
199+
{
200+
Dictionary<string, Type> types = new();
201+
202+
if (reflectionParams.AttemptReflection)
203+
{
204+
foreach (var assembly in reflectionParams.AssembliesToSearch)
205+
{
206+
foreach (var classType in assembly.DefinedTypes)
207+
{
208+
types.TryAdd(classType.Name, classType);
209+
}
210+
}
211+
212+
foreach (var type in reflectionParams.AdditionalTypes)
213+
{
214+
types.TryAdd(type.Name, type);
215+
}
216+
}
217+
218+
return types;
219+
}
174220
}
175221

176222
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)]

Tests/Resources/error.vmap

-64 KB
Binary file not shown.

Tests/Tests.cs

Lines changed: 18 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,8 @@
88
using Datamodel;
99
using System.Numerics;
1010
using DM = Datamodel.Datamodel;
11-
using System.Text;
1211
using System.Globalization;
13-
using ValveResourceFormat.IO.ContentFormats.ValveMap;
14-
using System.ComponentModel.DataAnnotations;
12+
using VMAP;
1513

1614
namespace Datamodel_Tests
1715
{
@@ -298,40 +296,27 @@ public static void TypedArrayAddingRemoving()
298296

299297

300298
[Test]
301-
public void Create_Datamodel_Vmap()
302-
{
303-
//using var datamodel = new DM("vmap", 29);
304-
//datamodel.PrefixAttributes.Add("map_asset_references", new List<string>());
305-
//datamodel.Root = new Element(datamodel, "root", classNameOverride: "CMapRootElement")
306-
//{
307-
// ["isprefab"] = false,
308-
// ["showgrid"] = true,
309-
// ["snaprotationangle"] = 15,
310-
// ["gridspacing"] = 64,
311-
// ["show3dgrid"] = true,
312-
// ["itemFile"] = true,
313-
// ["world"] = new Element(datamodel, "world", classNameOverride: "CMapWorld"),
314-
//};
315-
//
316-
//using var stream = new MemoryStream();
317-
//datamodel.Save(stream, "keyvalues2", 4);
318-
//Assert.That(stream.Length, Is.GreaterThan(0));
319-
320-
using var actual = DM.Load(Path.Combine(TestContext.CurrentContext.TestDirectory, "Resources", "cs2_map.vmap.txt"));
321-
322-
CMapRootElement root = (CMapRootElement)actual.Root;
299+
public void LoadVmap_Reflection_Binary()
300+
{
301+
var unserialisedVmap = DM.Load(Path.Combine(TestContext.CurrentContext.TestDirectory, "Resources", "cs2_map.vmap"));
302+
303+
Assert.AreEqual(unserialisedVmap.Root.GetType(), typeof(CMapRootElement));
304+
305+
CMapRootElement root = (CMapRootElement)unserialisedVmap.Root;
306+
307+
Assert.AreEqual(root.world.GetType(), typeof(CMapWorld));
308+
323309
var world = root.world;
324-
var prop = (CMapEntity)world.children[1];
325-
var propProperties = prop.EntityProperties;
326310

311+
var props = world.children.Where(i => i.ClassName == "CMapEntity").OfType<CMapEntity>().ToList();
312+
313+
var propProperties = props[0].EntityProperties;
327314
var classname = propProperties.Get<string>("classname");
328315

329-
var meshes = world.children.Where(i => i.ClassName == "CMapMesh");
316+
var meshes = world.children.Where(i => i.ClassName == "CMapMesh").OfType<CMapMesh>().ToList();
317+
var mesh = meshes[0];
330318

331-
//Assert.That(actual.PrefixAttributes.ContainsKey("map_asset_references"), Is.True);
332-
//Assert.That(actual.PrefixAttributes["map_asset_references"], Is.Empty);
333-
Assert.That(actual.Root, Is.Not.Null);
334-
Assert.That(actual.Root["world"], Is.Not.Null);
319+
Assert.That(unserialisedVmap.PrefixAttributes["map_asset_references"], Is.Not.Empty);
335320
}
336321

337322
public class NullOwnerElement
@@ -417,16 +402,6 @@ public void PropertyAccessByKey()
417402
Assert.That(myprop, Is.EqualTo(1337));
418403
}
419404

420-
[Test]
421-
public void PropertySetByKey_Throws()
422-
{
423-
var elem = new CustomElement();
424-
425-
var ex = Assert.Throws(typeof(InvalidOperationException), () => elem["MyProperty"] = 5);
426-
427-
Assert.That(ex.Message, Does.Contain("Cannot set the value of a property-derived attribute by key"));
428-
}
429-
430405
[Test]
431406
public void CanBeAssignedToDatamodelRoot()
432407
{
@@ -600,7 +575,7 @@ public void TF2_KeyValues2_1()
600575
[Test, TestCaseSource(nameof(GetDmxFiles))]
601576
public void Unserialize(string path)
602577
{
603-
var dm = DM.Load(path);
578+
var dm = DM.Load(path, Datamodel.Codecs.DeferredMode.Automatic);
604579
PrintContents(dm);
605580
dm.Dispose();
606581
}

Tests/Tests.csproj

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,6 @@
1414
<ItemGroup>
1515
<Compile Remove="Resources\vmap.cs" />
1616
</ItemGroup>
17-
<ItemGroup>
18-
<None Include="vmap.cs" />
19-
</ItemGroup>
2017
<ItemGroup>
2118
<ProjectReference Include="..\Datamodel.NET.csproj" />
2219
</ItemGroup>

Tests/vmap.cs renamed to Tests/ValveMap.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
using System.Numerics;
33
using DMElement = Datamodel.Element;
44

5-
namespace consoleTestApp.VMAP;
5+
namespace VMAP;
66

77
/// <summary>
88
/// Valve Map (VMAP) format version 29.
@@ -205,7 +205,7 @@ internal class CMapInstance : BaseEntity
205205
/// <summary>
206206
/// A target <see cref="CMapGroup"/> to instance. With custom tint and transform.
207207
/// </summary>
208-
public DMElement? target { get; set; }
208+
public CMapGroup? target { get; set; }
209209
public Datamodel.Color tintColor { get; set; } = new Datamodel.Color(255, 255, 255, 255);
210210
}
211211

0 commit comments

Comments
 (0)