Page MenuHomeFeedback Tracker

Extend Read / Write context
Feedback, UrgentPublic

Description

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

Details

Severity
None
Resolution
Open
Reproducibility
N/A
Operating System
Windows 7
Category
General
Steps To Reproduce

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

vadymbabii updated the task description. (Show Details)Mar 5 2024, 12:54 PM
vadymbabii edited Steps To Reproduce. (Show Details)
vadymbabii edited Steps To Reproduce. (Show Details)
Geez changed the task status from New to Feedback.Mar 6 2024, 11:01 AM
lava76 added a subscriber: lava76.Mar 17 2024, 2:19 PM

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.

vadymbabii added a comment.EditedMar 20 2024, 12:42 PM

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