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.
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.
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.
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.