Add a new method for the Serializer class with the possibility to read context with downcasted objects. The current implementation requires full variable type compatibility
Description
Details
- Severity
- None
- Resolution
- Open
- Reproducibility
- N/A
- Operating System
- Windows 7
- Category
- General
Actual Behavior:
// Create the serialize-based object FileSerializer file_w = new FileSerializer(); file_w.Open("$profile:test.txt", FileMode.WRITE); // Write complex object to it array<string> test_array_w = { "Hello", "World" }; file_w.Write(test_array_w); file_w.Close(); // Create another serializer FileSerializer file_r = new FileSerializer(); file_r.Open("$profile:test.txt", FileMode.READ); ref Class test_array_r; // Try to read it to the Base class object Print(file_r.Read(test_array_r)); // False file_r.Close(); Print("Test array read: " + test_array_r); array<string>.Cast(test_array_r).Debug(); // Error
Expected Behavior:
// Create the serialize-based object FileSerializer file_w = new FileSerializer(); file_w.Open("$profile:test.txt", FileMode.WRITE); // Write complex object to it array<string> test_array_w = { "Hello", "World" }; file_w.Write(test_array_w); file_w.Close(); // Create another serializer FileSerializer file_r = new FileSerializer(); file_r.Open("$profile:test.txt", FileMode.READ); ref Class test_array_r; // Try to read it into the Base class object Print(file_r.ReadEx(test_array_r)); // True (Extended method for backward compatibility) file_r.Close(); Print("Test array read: " + test_array_r); array<string>.Cast(test_array_r).Debug(); // Unsafe upcast, but should be the same as test_array_w
I understand that Read method spawns a new base class object, but what if we can pass an argument not as a ref variable but as a typename
->
class Serializer { bool Read(void var_in); // the existed method Class ReadEx(typename var_type); // Method that spawns and returns a new variable with type as the typename or null if error }
Event Timeline
This ticket seems to be based on a misunderstanding of how the serializers work in DayZ. For the serializer to know how to read something, it needs to know the structure of what it is reading, since written serialized data is just a binary blob with no structural information whatsoever. Class obviously lacks any structural info that the serializer could use to make sense of the binary data it is reading.
Hi lava76! Thanks for replying. Yeah, I believe I misunderstood some things, but I am still a little bit confused. Maybe the example I provided isn't the best. So here's another one
class CParent { int m_ParentInt; void CParent() { m_ParentInt = 1; } } class CChild : CParent { string m_ChildString; void CChild() { m_ParentInt = 10; m_ChildString = "Hello World"; } } void TestFunc() { ScriptRPC rpc = new ScriptRPC(); //I want to send the CChild object to the client only having the ref to CParent CParent child = new CChild(); rpc.Write(child); rpc.Send(player, 202011, true, identity); }
What type of data the serializer will covert to binary representation? CChild object or CParent? I haven't spent so much time investigating it, but I assume that the serializer must convert to binary based on the actual type of the object (CChild)
That's how Jacob's CF RPC wrapper works, right? (Only having ref to a Param object serializer writes it to context with an actual object type)
But when I try to receive it I can read the object only as СParent
override void OnRPC(PlayerIdentity sender, int rpc_type, ParamsReadContext ctx) { CParent parent; bool status = ctx.Read(parent); // status = true Print(parent.m_ParentInt); // Prints 10, but still can't downcast it to CChild. That's ok }
But when I try to read it to a child object (In this example I know which actual type I should expect from the ctx) Read method returns false.
override void OnRPC(PlayerIdentity sender, int rpc_type, ParamsReadContext ctx) { CChild child; bool status = ctx.Read(child); // status = false, child var is a null pointer, looks like ctx writes only a base object }
So basically I ask for the ability to write/read actual data structures to the Serializer Context only having the ref to the parent object. I know that the Read method will not work that way, but what if we can set the expected data type as a method parameter?
Hi,
What type of data the serializer will covert to binary representation? CChild object or CParent?
In this case, CChild. The actual binary data written will be the count of variables as int (4 bytes), followed by m_ParentInt (4 bytes), length of string m_ChildString as int (4 bytes), followed by the raw string.
But when I try to read it to a child object (In this example I know which actual type I should expect from the ctx) Read method returns false.
CParent only has one variable (m_ParentInt) so it cannot read all the data. So, either change CParent parent to CChild child and read into that, or add an (e.g.) Unserialize method that takes the ctx as argument and reads the data manually. E.g.
bool Unserialize(ParamsReadContext ctx) { int varCount; int parentInt; string childString; if (!ctx.Read(varCount)) return false; if (!ctx.Read(parentInt)) return false; if (!ctx.Read(childString)) return false; return true; }
The issue I am trying to resolve is the possibility of sending any type of data as a class object with a field with type Class
class Request { int m_Field1; string m_Field2; bool m_Field3; Class m_Data; } class Response { int m_Field1; string m_Field2; bool m_Field3; float m_Field4; Class m_Data; }
I use it as a generic solution for any of the RPC calls (I already figured it out with templates and had to resolve several strange EnScript bugs, but that's another issue, so)..) And that's the issue I faced with. I know I can use Params class for that sort of data, but every time I get a heart attack when I try to remember what which field means. So basically for me adding a new Serializer method with the possibility to write full bin data to the variable with parent type which stores the actual child object sounds very useful. But it still sounds usable in pretty specific cases like this one, so that's not a high priority and just a suggestion for future improvements
The issue isn't writing, the issue is reading.
To read back (unserialize) a binary blob of data, you need to know the structure of said data, meaning if you read it into a class, it should have compatible member variable types (although it'll read anything as long as expected number of variables and datasize matches, i.e. you can absolutely read a float as an int or vice versa, as it's 4 bytes in both cases, but there will be no type conversion, e.g. writing float 1.0 and reading it back as int will turn it into 1065353216).
So the only option you really have is to read all the data you can, and then throw the rest away if your class can't hold the data it can't read.
I understand this behavior, that's why I suggest adding a new Read method with a typename as an input argument. But probably it can cause issues with the EnScript's primitive types or classes with templates. Sorry for my English, it still isn't good enough to explain everything I want to explain) Btw, I see your point and understand that even the method I suggest doesn't help to resolve the issue. Looks like using templates is the only way to make it work. Thanks for your replies! Maybe we can also discuss this issue? :D https://feedback.bistudio.com/T173049