Showing posts with label State. Show all posts
Showing posts with label State. Show all posts

Sunday, 15 January 2023

Working-With-Byte-Arrays

In this post I will show how to read/write different data type values to and from a byte buffer. This should prove useful when working with block data or pages. In this scenario, pages describes database pages or similar block data mechanisms.


Top

In this post...

Top

How To Handle Typed Values

The aim is to read/write different typed values from a byte array. In C++ one can achieve this by using a union. In C# we achieve this by using a struct/class using explicit layout. Explicit layout is the key here as it ensures types are aligned correctly.

The basic idea is to have a number of byte values offset from zero to maximum bytes required. Then add basic types, such as int 16, int 32, int 64, float, etc all at offset zero. Then one can set a float value and read of the four byte values. The same can be done for most basic types, set the value and the read the corresponding byte values. To read typed values, simply populate the byte values accordingly then read the basic type info (int 16, int 32, int float, etc).

For those with a COM background this approach is similar to the VARIANT type.

Top

The Union Value Type


using System;
using System.Runtime.InteropServices;
namespace Blog
{
  [StructLayout(LayoutKind.Explicit)]
  public struct Value
  {
    [FieldOffset(0)] public byte B0;
    [FieldOffset(1)] public byte B1;
    [FieldOffset(2)] public byte B2;
    [FieldOffset(3)] public byte B3;
    [FieldOffset(4)] public byte B4;
    [FieldOffset(5)] public byte B5;
    [FieldOffset(6)] public byte B6;
    [FieldOffset(7)] public byte B7;

    [FieldOffset(0)] public float Float;
    [FieldOffset(0)] public double Double;
    [FieldOffset(0)] public Int16 Int16;
    [FieldOffset(0)] public Int32 Int32;
    [FieldOffset(0)] public Int64 Int64;
  }
}

The above struct declaration shows eight bytes, B0-B7 for data transfer. If using the decimal type this will need to be expanded to include 16 bytes! As can be seen, B0-B7 is byte aligned and offset accordingly.

Note also, that basic types float, double, Int16, etc all start at offset zero. So, if one sets the Float field, the corresponding 4 bytes can be read from B0-B3. To reverse the action, set fields B0-B3, then read the float value.

It is imperative that one knows how many bytes are required for a typed value. The above structure should cover most data types bar decimal. DateTime values can also be stored by first converting to long and using the Int64 field or bytes B0-B7 to recreate.

Top

Testing The Union Value Type

The above can be tested using the following code...

static void RawTest()
{
  Value value = new Value();
  byte[] data = new byte[128];

  int cursor = 0;
  
  // *********************************************************************************************
  // Writing values to the data byte array.
  // *********************************************************************************************
  Int16 v1 = Int16.MaxValue - 1067;
  Int32 v2 = Int32.MaxValue - 10067;
  float v3 = float.MaxValue - 1.07896f;

  // Write an Int16 value to the data byte array.
  // First set the Value's Int16 field, then read two Value bytes.
  value.Int16 = v1;
  data[cursor++] = value.B0;
  data[cursor++] = value.B1;

  // Write an Int32 value to the data byte array.
  // First set the Value's Int32 field, then read four Value bytes.
  value.Int32 = v2;
  data[cursor++] = value.B0;
  data[cursor++] = value.B1;
  data[cursor++] = value.B2;
  data[cursor++] = value.B3;

  // Write a float value to the data byte array.
  // First set the Value's Float field, then read four Value bytes.
  value.Float = v3;
  data[cursor++] = value.B0;
  data[cursor++] = value.B1;
  data[cursor++] = value.B2;
  data[cursor++] = value.B3;

  
  // *********************************************************************************************
  // Reading values from the data byte array.
  // *********************************************************************************************
  cursor = 0;
  
  // Read an Int16 from the data byte array.
  // Set Value's first two byte fields, then read the Value's Int16 field.
  value.B0 = data[cursor++];
  value.B1 = data[cursor++];
  var v1Result = value.Int16;

  // Read an Int32 from the data byte array.
  // Set Value's first four byte fields, then read the Value's Int32 field.
  value.B0 = data[cursor++];
  value.B1 = data[cursor++];
  value.B2 = data[cursor++];
  value.B3 = data[cursor++];
  var v2Result = value.Int32;

  // Read a float from the data byte array.
  // Set Value's first four byte fields, then read the Value's Float field.
  value.B0 = data[cursor++];
  value.B1 = data[cursor++];
  value.B2 = data[cursor++];
  value.B3 = data[cursor++];
  var v3Result = value.Float;

  Console.WriteLine(v1);
  Console.WriteLine(v1Result);
  Console.WriteLine(v2);
  Console.WriteLine(v2Result);
  Console.WriteLine(v3);
  Console.WriteLine(v3Result);
}
Top

Writing Values To A Byte Array

Figure 2 illustrates a byte array filled with values from the previous test code. As the diagram illustrates, data values are byte aligned accordingly. That is, a 16 bit-integer requires 2 bytes, a float or 32-bit integer requires 4 bytes. This approach works well for data blocks comprising of byte arrays. Simply create a byte array, write values and save to disk. Conversely, load a byte array block from disk into memory, then proceed to read actual values.

Top

Improving The Value Interface

The current method of reading/writing values is somewhat verbose. One needs to track the byte array offset (cursor) and the offset to add following a read or write. In addition, explicitly reading and writing to the Value's byte fields (B0...BN) is tiresome and error-prone. The following should help alleviate these problems...

  • Specify the byte array and initial offset.
  • Specify a cursor that is relative to the specified initial offset.
  • Update the cursor accordingly following a read or write operation.
  • Allow the cursor's position to be set manually, the cursor will always be relative to the specified offset.

The following class, ByteBuffer, implements the above features.

using System;

namespace Blog
{
  /// <summary>
  /// Allows values to read/written to/from a byte array.
  /// Specify a byte buffer and initial offset in the constructor.
  /// Data will be read/written at this offset.
  /// The class uses a cursor to indicate current read/write position.
  /// The cursor is always offset by the offset specified in the constructor.
  /// </summary>
  public class ByteBuffer
  {
    private Value _value = new Value();
    private int _internalCursor;
    private int _offset;
    private readonly byte[] _buffer;

    public int Cursor => _internalCursor - _offset;

    public ByteBuffer(byte[] buffer, int offset)
    {
      _buffer = buffer;
      _offset = offset;
      _internalCursor = offset;
    }

    public ByteBuffer SetCursor(int position)
    {
      _internalCursor = _offset + position;
      return this;
    }

    public ByteBuffer Int16(Int16 value)
    {
      _value.Int16 = value;
      _buffer[_internalCursor++] = _value.B0;
      _buffer[_internalCursor++] = _value.B1;
      return this;
    }

    public ByteBuffer Int16(out Int16 result)
    {
      _value.B0 = _buffer[_internalCursor++];
      _value.B1 = _buffer[_internalCursor++];
      result = _value.Int16;
      return this;
    }

    public ByteBuffer Int32(Int32 value)
    {
      _value.Int32 = value;
      _buffer[_internalCursor++] = _value.B0;
      _buffer[_internalCursor++] = _value.B1;
      _buffer[_internalCursor++] = _value.B2;
      _buffer[_internalCursor++] = _value.B3;
      return this;
    }

    public ByteBuffer Int32(out Int32 result)
    {
      _value.B0 = _buffer[_internalCursor++];
      _value.B1 = _buffer[_internalCursor++];
      _value.B2 = _buffer[_internalCursor++];
      _value.B3 = _buffer[_internalCursor++];

      result = _value.Int32;
      return this;
    }

    public ByteBuffer Float(float value)
    {
      _value.Float = value;
      _buffer[_internalCursor++] = _value.B0;
      _buffer[_internalCursor++] = _value.B1;
      _buffer[_internalCursor++] = _value.B2;
      _buffer[_internalCursor++] = _value.B3;
      return this;
    }

    public ByteBuffer Float(out float result)
    {
      _value.B0 = _buffer[_internalCursor++];
      _value.B1 = _buffer[_internalCursor++];
      _value.B2 = _buffer[_internalCursor++];
      _value.B3 = _buffer[_internalCursor++];
      result = _value.Float;
      return this;
    }
  }
}
Top

Using The ByteBuffer Class

Using the ByteBuffer class is fairly straightforward. Simply call the constructor with a byte array and offset. No data can be read or written before the offset.

A sample test/driver program now follows...

static void Main(string[] args)
{
  byte[] data = new byte[128];
  ByteBuffer buffer = new ByteBuffer(data, 5);

  Int16 v1 = Int16.MaxValue - 1067;
  Int32 v2 = Int32.MaxValue - 10067;
  float v3 = float.MaxValue - 1.07896f;

  int bytesWritten = buffer
    .Int16(v1)
    .Int32(v2)
    .Float(v3)
    .Cursor;
  Console.WriteLine($"{bytesWritten} bytes written to buffer.");

  int bytesRead = buffer
    .SetCursor(0)
    .Int16(out var v1Read)
    .Int32(out var v2Read)
    .Float(out var v3Read)
    .Cursor;

  Console.WriteLine($"{bytesRead} bytes read from buffer.");
  Console.WriteLine($"v1Write:{v1} - v1Read:{v1Read}");
  Console.WriteLine($"v2Write:{v2} - v2Read:{v2Read}");
  Console.WriteLine($"v3Write:{v3} - v3Read:{v3Read}");
}

The above code wites the following to the console.

Notice how the test program uses an offset of 5 when constructing the ByteBuffer. The correct bytes read/wrriten of ten is still returned. The first five bytes in this example will be zeroed.

Top

Saturday, 16 October 2021

C# Saving State

In this blog I show how one can save program state easily in XML format. Existing .NET code allows this but is somewhat hard to use. I have used the method I will show in many projects.

Overview

For this to work, you need to ensure that all data you wish to persist is can be converted to XML. This is usually as simple as placing the XmlSerializable attribute on a class. Not all classes can be serialized without additional effort. Generally best to stick to non-generic types to ensure reliability.

For this example I use a single state class. I also create a # define that allows me to either save or load data accordingly. The code will make sense, hopefully.

The code

I define a simple address class. Note, the class is a simple data class and contains no methods. I do include an overridden ToString method, useful when debugging.

[Serializable]
public class Address
{
  public int Number;
  public string Street;
  public string PostCode;

  public override string ToString()
  {
    return $"{Number}, {Street}. {PostCode}";
  }
}
Top

Next I declare a customer class. Customers are everywhere so the example seems apt.

[Serializable]
public class Customer
{
  public string FirstName;
  public string LastName;
  public Address Address = new Address();

  public override string ToString()
  {
    return $"{LastName}, {FirstName}, {Address}";
  }
}
Top

Next I define the state class. This would contain state required to run your software.


[Serializable]
public class State
{
  public Customer[] Customers;

  public State()
  {
    this.Customers = new Customer[0]; 
  }

  public State XmlSave()
  {
    XmlSerializer s = new XmlSerializer(typeof(State));
    using (var sw = new StreamWriter("state.xml"))
    {
      s.Serialize(sw, this);
      return this;
    }
  }

  public static State XmlLoad()
  {
    XmlSerializer xs = new XmlSerializer(typeof(State));
    using (var sr = new StreamReader("state.xml"))
    {
      return (State)xs.Deserialize(sr);
    }
  }
}
Top

The State class, as you might expect, maintains all state required for my program to run. The XmlSave function is a member function, so that one can call state.XmlSave(). That is, you need a state instance to save.

The XmlLoad function is static. This is simply because when your program runs you will not have a state instance. So, you will typically want to load and create a state instance. Bootstrap/startup code is simplified as one can simply code "var state = State.XmlLoad();"

Top

The main code, for testing follows...

class Program
{
  static State CreateData()
  {
    return new State()
    {
      Customers= new Customer[]
      {
        new Customer() {
          FirstName = "Karl",
          LastName = "Page",
          Address = new Address() {
            Number=80,
            PostCode="KKJ486",
            Street="Arcacia Avenue" }
        },

        new Customer() {
          FirstName = "Cecilia",
          LastName = "Page",
          Address = new Address() {
            Number=80,
            PostCode="KKJ486",
            Street="Arcacia Avenue" }
        },

        new Customer() {
          FirstName = "Fred",
          LastName = "Bloggs",
          Address = new Address() {
            Number=40,
            PostCode="LLJ86K",
            Street="Nowhere Avenue" }
        }
      }
    };
  }

  static void Main(string[] args)
  {
#if SAVE
    State s = CreateData();
    s.XmlSave();
#else
    State s = State.Load();
#endif
  }
}
Top

Testing

To test run the program as is using F5 to debug.

A file, state.xml should appear under the debug folder. You may have to dig, depends upon project settings.

Make sure you add the line #define SAVE before any usings, at the top of the progam file. This should write state to the the state.XML file. Remove #define SAVE to invoke loading.

Top

My XML appears as follows...

<?xml version="1.0" encoding="UTF-8"?>
<State xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Customers>
 <Customer>
  <FirstName>Karl</FirstName>
  <LastName>Page</LastName>
  <Address>
   <Number>80</Number>
   <Street>Arcacia Avenue</Street>
   <PostCode>KKJ486</PostCode>
  </Address>
 </Customer>

 <Customer>
  <FirstName>Cecilia</FirstName>
  <LastName>Page</LastName>
  <Address>
   <Number>80</Number>
   <Street>Arcacia Avenue</Street>
    lt;PostCode>KKJ486</PostCode>
  </Address>
</Customer>

 lt;Customer>
 <FirstName>Fred</FirstName>
 <LastName>Bloggs</LastName>
 <Address>
  lt;Number>40</Number>
  <Street>Nowhere Avenue</Street>
  <PostCode>LLJ86K</PostCode>
  </Address>
 </Customer>

</Customers>
</State>

Friday, 14 May 2021

Functional Programming

What is functional programming?

Functional programming places functions first. All aspects of the resultant software is built upon functions. In contrast, Object-Oriented development places objects first. In this case, objects encapsulate state and potentially, behaviour. In functional programming, data and behaviour are typically kept separate. In my experience, the two techniques can be summarised as follows.

  • Object-Oriented

    Well-suited for user interface development as widgets (e.g. buttons, text input, etc) can maintain state such as text, event handlers for when say a button is clicked or text is editied.

    Data types such as stacks, queues, dictionaries, etc. Managing state and exposing behaviour in single place makes sense.

  • Functional

    Better suited where partitioning state across different objects is non-sensical or becomes problematic.

    Data types are complex, for example invoice processing, game state and so on. More complex data types are better manipulated using functions rather than "attaching" functions to the data type (Functions attached to a data type are typically known as methods in Object-Oriented development).

Top

What is a function?

A function accepts one or more inputs and generates a single output. It is also possible for a function to accept zero inputs and just return a value. The main take should be that a function can only create an output based upon the input values supplied. This is important! What this means is that a function cannot use outside state (global state) to generate an output. A function that only uses supplied inputs to generate an output is known as a Pure Function

Top

How do I code a good function?

To create a solid, sound function that can be reasoned about ceratin rules must be followed.

  • Use pure functions

    As mentioned, a pure function generates an output based purely upon its inputs. Using global state is forbidden. Pure functions go a step further, executing code that produces any side-effects is forbidden. This includes writing to the console, writing to log files, updating a database. In short, a pure function should always produce the same output given the supplied inputs. No side-effects, no using global variables. In pure functional programming even throwing exceptions is no go. Makes sense as throwing an exception is a side-effect which goes against the notion of a pure function.

    An exception is really just a goto on steriods. Exceptions can make following program flow and debugging difficult. Well-written software shouldn't need debugging. Sure, debugging a new function whilst still in development is likely a must. Once the function has been tested you should be able to trust it and move on.

    There are ways to implement state changes whilst still adhering to the rules. I shall go into details later,

  • Use immutable data

    Mutable data is data than can change in place. I remember my C/C++ days when dealing with strings. It was common to modify existing strings. This is more performant than creating new string based upon old strings.

    Times have changed, as have data structures, memory and so on. Immutable data is now the way forward. This essentially means once you have created some data, an object, record whatever your language permits, it never changes. If you need different values, you create new data, possibly based upon the original data.

    Take some simple data, the data represents X and Y coordinates in 2D space. Using C#, the code to represent the data is as follows.

    
    public struct Point
    {
      // get and set are C# constructs that allow one to set or get a data value.
      // get allows one to read a data value.
      // set allows one to update (write to) a data value.
      public int X { get; set; }
      public int Y { get; set; }
    
      public Point(int x, int y)
      {
        this.X = x;
        this.Y = y;
      }
    
      public void Offset(int xOffset, int yOffset)
      {
        this.X += xOffset;
        this.Y += yOffset;
      }
    }
    

    Now assume the following test class

    
    public static class TestPoint
    {
      public static void Test1()
      {
        Point pt = new Point(20, 40);
        pt.X = 1000;
        pt.Offset(0,20);
        // pt now contains the values x=1000 and y=60.
      }
    }
    
    Top

    In the above code, pt was created to have an X value of 20 and a Y value of 40. The second statement assignes 1000 to X. This directly modifies the pt variable. This is known as in-place modification. The ability to modify data content in-place makes the data structure mutable. The code also contains an Offset method. This method adds x and y offsets to the existing data.

  • How do I refactor mutable code to immutable code?

    In this case, the transition from mutable to immutable code is easy. Make all data fields readonly and supply a constructor to initialise the data fields. The following code example illustrates the idea.

    
    public struct Point
    {
      // A field that contains only a get construct is deemed to be readonly.
      // The field value may only be assigned via the constructor.
      public int X { get; }
      public int Y { get; }
    
      public Point(int x, int y)
      {
        this.X = x;
        this.Y = y;
      }
    
      public Point WithOffset(int xOffset, int yOffset)
      {
        return new Point(X + xOffset, Y + yOffset);
      }
    }
    
    public static class TestPoint
    {
      public static void Test1()
      {
        // Create a pt variable as per previous example.
        Point pt = new Point(20, 40);
        
        // Unable to modify the pt variable as fields are readonly.
        // We need to create a new data, i.e. a new instance of Point.
        // We create a new point by adding 1000 to the current X and zero to the Y value.
        Point ptNew = pt.WithOffset(1000, 0);
      }
    }
    
    

    Notice how the new code doesn't quite behave in the same way as the previous code. In the previous code we directly set the X value to a 1000. To achieve the same result in the new, immutable data we need to add a new function, let's call it WithX.

    public struct Point
    {
      // A field that contains only a get construct is readonly.
      // The field value may only be assigned via the constructor.
      public int X { get; }
      public int Y { get; }
    
      public Point(int x, int y)
      {
        this.X = x;
        this.Y = y;
      }
    
      public Point WithOffset(int xOffset, int yOffset)
      {
        return new Point(X + xOffset, Y + yOffset);
      }
      
      public Point WithX(int x)
      {
        return new Point(x, Y);
      }
    }
    
    
Top