[Help] Creating a protected object

Hello everyone!

I am reading video frame data from the network and would like to create a protected object to hold the frame data (as well as some frame state metadata). A protected object (or equivalent solution) is desired because I am reading video data in a task and rendering the data in the main program unit.

I would like to use the code below but I am getting a compiler error:
render.adb: protected function cannot modify its protected object

Here is the code for my protected object:

type Frame_Data_Type is array (Natural 1 .. 1024) of Byte; 
protected Frame is
   function Is_New return Boolean;
   function Get return Frame_Data_Type;
   procedure Write (Data : Frame_Data_Type);
private
   Frame_Data : Frame_Data_Type := (Others => 0);
   New_Frame  : Boolean := False;
end Frame;
protected body Frame is
   function Is_New return Boolean is
   begin
      return New_Frame;
   end;
    
   function Get return TIC80_Frame_Data_Type is
   begin
      New_Frame := False;
      return Frame_Data;
   end;
    
   procedure Write (Data : Frame_Data_Type) is
   begin
      Frame_Data := Data; 
      New_Frame := True;
   end;
end Frame;

The goal is to use the ā€˜New_Frame’ variable to track the ā€œfreshnessā€ of the video frame. That is, as soon as the frame is read (using ā€˜Get’) by the renderer, the condition of the ā€˜New_Frame’ variable should be set to false.

I don’t understand why the compiler does not allow this code. If anyone has any suggestions for an alternative solution (or idea) please let me know.

Thanks!
N

There are protected functions and protected procedures. Functions see the data in the protected object as read-only, while procedures are allowed to read and write the protected data. (The idea is that the protection allows any number of concurrent function calls (readers) but only only one concurrent procedure call (one writer). Change your function Get into a procedure Get (with an ā€œoutā€ parameter) if it should be able to modify the protected data.

1 Like

That works great. Thanks for the explanation Niklas!

Note that your declaration of Frame_Data_Type is invalid.

While Niklas’ answer is correct and works, it looks to me like you probably want a pair of entries:

type Frame_Data is array (1 .. 1_024) of Byte;

protected Frame_Control is
   function Is_New return Boolean;
   -- Returns True if a frame supplied by Put has not been obtained by Get; False otherwise

   entry Put (Frame : in Frame_Data);
   -- Blocks until not Is_New
   -- Makes Get return Frame and Is_New return True
   -- Will not overwrite a frame that has not been obtained by Get

   entry Get (Frame : out Frame_Data);
   -- Blocks until Is_New
   -- Makes Is_New return False
   -- Returns the most recent Frame supplied by Put
private -- Frame_Control
   Data      : Frame_Data;
   New_Frame : Boolean := False;
end Frame_Control;

protected body Frame_Control is
   function Is_New return Boolean is (New_Frame);

   entry Put (Frame : in Frame_Data) when not New_Frame is
      -- Empty
   begin -- Put
      Data := Frame;
      New_Frame := True;
   end Put;

   entry Get (Frame : out Frame_Data) when New_Frame is
      -- Empty
   begin -- Get
      New_Frame := False;
      Frame := Data;
   end Get;
end Frame_Control;

This allows the producer and consumer tasks to avoid polling. The producer task can contain

loop
   -- Obtain Frame
   Frame_Control.Put (Frame => Frame);
end loop;

and the consumer

loop
   Frame_Control.Get (Frame => Frame);
   -- Use Frame
end loop;

Note that your declaration of Frame_Data_Type is invalid.

Thanks. I fixed this.

I was assuming that my functions ā€˜Get’ and ā€˜Write’ could not be called by two tasks simultaneously. And if so, one task would block and wait until the other task has finished the protected object subprocedure call. Is this behavior correct?

That’s correct, they can’t be called simultaneously. However, what if the task that calls Get is much faster than the one that calls Put. You would have:

  • task 1 calls Put
  • task 2 calls Get (so far so good)
  • task 2 calls Get again (but what happens now, there is nothing to return)

Using entries allows the second call to Get to actually block the task (and consume no CPU) until there is indeed something available. And would block task 1 if it was faster and the previous item had not been read yet.

The problem becomes ever more noticeable if you have more than one task calling Get for instance.

1 Like

Note that your declaration of Frame_Data_Type is invalid.

Thanks. I fixed this.

Not that I can see.

Of course multiple tasks can call the same protected object at the same time. That’s the whole point of a protected object. But when calling procedures and entries, at most one of those calls may proceed. As @ebriot pointed out, if Put and Get are procedures, and one of the tasks is faster than the other, then either the producer will overwrite unprocessed frames, or the consumer will process the same frame more than once, unless they poll on Is_New. Using entries takes care of that for you, without polling, so entries are often better for this kind of thing.