24/02: SdlSimple highlights
previous: sdlSimple
ummmm...i thought i would highlight some things i learned while writing this library:
Importing functions from external c++ libraries
a couple of examples:
[<DllImport("libpcxf", EntryPoint="colour")>]
extern int colourC(byte r, byte g, byte b)
//useage:
let yellow = colourC(255uy, 255uy, 0uy)
[<DllImport("libpcxf")>]
extern unit setScreen(IntPtr pixels, int width, int height, int depth)
//usage:
let (screen : IntPtr) = Sdl.SDL_SetVideoMode(640, 480, 32, Sdl.SDL_DOUBLEBUF ||| Sdl.SDL_HWPALETTE)
let (surface : Sdl.SDL_Surface) = (Marshal.PtrToStructure(screen, typeof<Sdl.SDL_Surface>)) :?> Sdl.SDL_Surface
let buffer = surface.pixels
setScreen(buffer, sdlWidth, sdlHeight, sdlDepth)
extern int colourC(byte r, byte g, byte b)
//useage:
let yellow = colourC(255uy, 255uy, 0uy)
[<DllImport("libpcxf")>]
extern unit setScreen(IntPtr pixels, int width, int height, int depth)
//usage:
let (screen : IntPtr) = Sdl.SDL_SetVideoMode(640, 480, 32, Sdl.SDL_DOUBLEBUF ||| Sdl.SDL_HWPALETTE)
let (surface : Sdl.SDL_Surface) = (Marshal.PtrToStructure(screen, typeof<Sdl.SDL_Surface>)) :?> Sdl.SDL_Surface
let buffer = surface.pixels
setScreen(buffer, sdlWidth, sdlHeight, sdlDepth)
Marshaling an array of structs from c++ to f#
'getPalette' is a c++ function that takes a file location as argument and returns an array of SDL_Color structs (which are 32-bit int's basically).
to access the returned array in f# it would seem you have to increment the returned pointer and reconstruct the array.
[<DllImport("libpcxf")>]
extern IntPtr getPalette(string pal)
let mutable (palette : IntPtr) = (getPalette(file))
let colours = [|0..255|] |> Array.map (fun i -> Sdl.SDL_Color())
for i in [0..255] do
colours.[i] <- (Marshal.PtrToStructure(palette, typeof<Sdl.SDL_Color>) :?> Sdl.SDL_Color)
palette <- IntPtr(palette.ToInt32() + sizeof<Sdl.SDL_Color>)
F# keywords are not allowed as method names
because 'type' is a f# reserved keyword i was unable to extract the event type from an sdl event structure with 'event.type'
i was able to overcome this by writing a short c# library that did this for me.
so:
(!event).type
became:
GetEventType.Get(!event)
using this c# dll:
using Tao.Sdl;
public class GetEventType {
public static int Get(Sdl.SDL_Event e) {
return e.type;
}
}
i encountered a similar problem with the 'val' keyword which was solved using this dll:
using Tao.Sdl;
public class GetJaxisValue {
public static short Get(Sdl.SDL_Event e) {
return e.jaxis.val;
}
}
Overloaded functions
overloaded functions are not presently availible in f# but you can match on types which achieves a similar result:
let write c (data : obj) (x, y) =
match data with
| :? string -> writeString(x, y, (data :?> string), c)
| :? int -> writeInt(x, y, (data :?> int), c)
| :? double -> writeDouble(x, y, (data :?> double), c)
| _ -> failwith "data type not supported."
Events
for those unfamiliar with SDL (www.libsdl.org) it requires you to create an infinite loop that listens for sdl events and reacts to them. this can be seen in the previous entry under 'sdl event loop'.
in this loop i convert the wanted native SDL events into f# events of the type union 'sdlevent':
type sdlevent =
| KeyUp of int
| KeyDown of int
| MouseMove of int * int
...etc...
and fire them into the 'events' IEvent.
you can then listen for these events using the regular f# Event methods:
events |> Event.listen (fun e ->
match e with
| KeyDown x -> printf "%s pressed.\n" (scancodeToKey x)
| KeyUp x -> printf "%s released.\n" (scancodeToKey x)
| _ -> ()
)
Drawing
SdlSimple keeps a list of functions in 'drawevents' and executes each one on every iteration of the sdl event loop like so:
for (s, e) in drawevents do e()
you draw to the screen by adding a function that utilizes one or more drawing methods (pixel, line, write, rectangle, colour) to the 'drawevents' list using the 'drawfire' event firing function.
this will add the 'hello' function to the 'drawevents' list thus writing "hello" to the screen:
drawfire (Draw ("hello", (fun () ->
write (colour 255 255 255) "hello" (100, 100)
)))
and this will remove it from the same list; erasing it from the screen:
drawfire (Erease "hello")
Event and drawing DSL's
this is most likely an abuse of the f# monad syntax but i believe it allows for a concise way of drawing and responding to simple events:
//draw monad:
type DrawMonad(name) =
member this.Delay(f) = drawfire (Draw (name, f))
member this.Zero() = ()
let draw = DrawMonad("draw")
let drawn name = DrawMonad(name)
//event monad:
type EventMonad(evnt) =
member this.Delay(f) =
events |> Event.listen (fun e ->
match e with
| x when x = evnt -> f()
| _ -> ()
)
member this.Zero() = ()
let onevent e = EventMonad(e)
//clear the screen:
let cls (str : obj) =
match str with
| :? string -> drawfire (Erase (str :?> string))
| _ -> drawfire (Erase "draw")
the above classes allow you to write code like this:
//draw a green triangle:
drawn "triangle" {
let green = colour 0 255 0
[(150, 100); (200, 200); (100, 200); (150, 100)] |> Seq.reduce (line green) |> ignore
}
//erease the triangle when 'enter' is pressed:
onevent (KeyDown 13) {
cls "triangle"
}
Checking for a file in multiple locations
List.find returns the fist element for which the given function returns true and throws a 'KeyNotFoundException' if no such element exists.
try ["/usr/local/share/libpcxf/palette.gpl"; "/usr/share/libpcxf/palette.gpl"; "palette.gpl"]
|> List.find System.IO.File.Exists |> setPalette |> ignore with | _ -> ()