Search code examples
bindingsdlunionadaunchecked

Ada SDL Unchecked Union passed from C binding


I'm using AdaSDL2, a third-party binding to a C library. Events in SDL are handled via a union, as seen at this reference:

http://www.willusher.io/sdl2%20tutorials/2013/08/20/lesson-4-handling-events/

Problem is that the binding defines the SDL_Event type as an Unchecked Union and as such you can't check a discriminant to see what type it is. Here is the definition of the type:

type SDL_Event (discr : C.unsigned := 0) is record
  case discr is
     when 0 =>
        typ : aliased Uint32;
     when 1 =>
        common : aliased SDL_CommonEventRecord;
     when 2 =>
        window : aliased SDL_WindowEventRecord;
     when 3 =>
        key : aliased SDL_KeyboardEventRecord;
     when 4 =>
        edit : aliased SDL_TextEditingEventRecord;
     when 5 =>
        text : aliased SDL_TextInputEventRecord;
     when 6 =>
        motion : aliased SDL_MouseMotionEventRecord;
     when 7 =>
        button : aliased SDL_MouseButtonEventRecord;
     when 8 =>
        wheel : aliased SDL_MouseWheelEventRecord;
     when 9 =>
        jaxis : aliased SDL_JoyAxisEventRecord;
     when 10 =>
        jball : aliased SDL_JoyBallEventRecord;
     when 11 =>
        jhat : aliased SDL_JoyHatEventRecord;
     when 12 =>
        jbutton : aliased SDL_JoyButtonEventRecord;
     when 13 =>
        jdevice : aliased SDL_JoyDeviceEventRecord;
     when 14 =>
        caxis : aliased SDL_ControllerAxisEventRecord;
     when 15 =>
        cbutton : aliased SDL_ControllerButtonEventRecord;
     when 16 =>
        cdevice : aliased SDL_ControllerDeviceEventRecord;
     when 17 =>
        quit : aliased SDL_QuitEventRecord;
     when 18 =>
        user : aliased SDL_UserEventRecord;
     when 19 =>
        syswm : aliased SDL_SysWMEventRecord;
     when 20 =>
        tfinger : aliased SDL_TouchFingerEventRecord;
     when 21 =>
        mgesture : aliased SDL_MultiGestureEventRecord;
     when 22 =>
        dgesture : aliased SDL_DollarGestureEventRecord;
     when 23 =>
        drop : aliased SDL_DropEventRecord;
     when others =>
        padding : aliased SDL_Event_padding_array;
  end case;
end record;
pragma Convention (C_Pass_By_Copy, SDL_Event);
pragma Unchecked_Union (SDL_Event);

So when the example code in the above reference says to check e.type, I don't have a related value in the Ada type. When I try this:

procedure Process_Events is
    E : access SDL_Event;
begin
    while SDL_PollEvent(E) = 1 loop
        if E.discr = SDL_SHUTDOWN then
            Ada.Text_IO.Put("Shutdown requested.");
    Ada.Task_Identification.Abort_Task(Ada.Task_Identification.Current_Task);
        end if;
    end loop;
end Process_Events;

GNAT says "cannot reference discriminant of Unchecked_Union. I've tried a couple of different ideas to figure out what type of Event it is but so far none seem both feasible and efficient. Is there a common solution to this, or does anybody have any ideas? Thanks.


Solution

  • Looks to me as though all the record types that are used in the discriminated components start with (e.g.)

    type SDL_CommonEventRecord is record
       typ       : aliased Uint32;
       timestamp : aliased Uint32;
    end record;
    

    and these are going to be overlaid, so the typ is the first 4 bytes of the SDL_Event; that is, typ, common.typ, window.typ all refer to the same data in store.

    I guess you’re supposed to write a case statement to handle this:

    case E.typ is
       when SDL_WINDOWEVENT =>
          -- use E.window components
       when SDL_KEYDOWN =>
          -- use E.key components for key-down
       when SDL_KEYUP =>
          -- use E.key components for key-up
       ...
    end case;