[m-users.] Ignoring !IO in an FFI binding, aceptable or hideously bad?
M McDonough
foolkingcrown at gmail.com
Mon Feb 17 12:25:34 AEDT 2025
> On 16 Feb 2025, at 11:38, Sean Charles (emacstheviking) <objitsu at gmail.com> wrote:
> I am forced to initialise to `no` because until the Raylib library is initialised, loading fonts doesn't work, however, I have experimented with a second version of load_font in my binding that does not use !IO:
The way you should generally get around this is by including the IO
state in the native code (you can simply ignore the IO variables, or
assign the output to the input to make it "feel" correct) or make the
predicate impure. You really shouldn't lie to the compiler about the
IO-ness of an operation.
This means that all asset loading will need IO or be impure. This is
correct, because it does. It's also useful for anyone using the
library, as they can see the true nature of what they are doing.
This sometimes will work OK to lie about this kind of thing because
the Mercury compiler is greedier about including unnecessary
statements and keeping your statements in the source code order than
some languages, but you can get correctness issues if you abuse this.
This is a very easy footgun you can see with the bitmap and array
modules of the stdlib, with which it is very easy to get unpredictable
or unexplainably incorrect results.
> They both work, and it means that I can now load fonts by adding an initialise call to the module and then loading fonts without having an !IO context. I think I might change the mutable to be a `univ` on the grounds that after module initialisation, I know it will contain a fully populated `font_state`. I envisaged the new call looking something like this:
>From my game projects in Mercury, I've come to believe that it's
really best to not do any of this. Have the code that needs assets
call the reading/loading functions with the name/path/descriptor of
exactly what it needs, and have them manage where the assets go. If
they call it twice, then they get a second copy, because that's what
they asked for (and might be what they want, for a variety of
reasons). Importantly, this makes it easier for the calling code (or
another module or system you add to your library) to handle things
like resolving dependencies and ensure certain assets are only loaded
once (if that is, in fact, what they want).
My method for managing this was to build up a set of the asset names
for each asset type, and then map that to an assoc_list of io.res(T)
of the assets loaded, then process that and convert it to either a
tree of the loaded assets, or print all errors that were found and
exit. This helped a lot with keeping the asset loading code separate
from the dependency management, and keep things that use IO sectioned
off from things that don't. This was huge because asset loading was
the slowest part of my game engine (the only operation that wasn't
"instant"), and this also let me easily parallelize it with very
little extra effort (especially big for loading sound files, that part
was what made my games go from instantly starting to the user waiting
a second or more for it to begin). I wouldn't want to bake that kind
of thing into every asset loading component, and really I wouldn't
want to make any of it implicit either.
More information about the users
mailing list