Refine drawing API semantics to pass drawing * instead of void *

This changes the drawing API so that implementations receive a
`drawing *` pointer with each call, instead of a `void *` pointer as
they did previously. The `void *` context pointer has been moved to be
a member of the `drawing` structure (which has been made public), from
which it can be retrieved via the new `GET_HANDLE_AS_TYPE()` macro. To
signal this breaking change to downstream front end authors, I've
added a version number to the `drawing_api` struct, which will
hopefully force them to notice.

The motivation for this change is the upcoming introduction of a
draw_polygon_fallback() function, which will use a series of calls to
draw_line() to perform software polygon rasterization on platforms
without a native polygon fill primitive. This function is fairly
large, so I desired that it not be included in the binary
distribution, except on platforms which require it (e.g. my Rockbox
port). One way to achieve this is via link-time optimization (LTO,
a.k.a. "interprocedural optimization"/IPO), so that the code is
unconditionally compiled (preventing bit-rot) but only included in the
linked executable if it is actually referenced from elsewhere.
Practically, this precludes the otherwise straightforward route of
including a run-time check of the `draw_polygon` pointer in the
drawing.c middleware. Instead, Simon recommended that a front end be
able to set its `draw_polygon` field to point to
draw_polygon_fallback(). However, the old drawing API's semantics of
passing a `void *` pointer prevented this from working in practice,
since draw_polygon_fallback(), implemented in middleware, would not be
able to perform any drawing operations without a `drawing *` pointer;
with the new API, this restriction is removed, clearing the way for
that function's introduction.

This is a breaking change for front ends, which must update their
implementations of the drawing API to conform. The migration process
is fairly straightforward: every drawing API function which previously
took a `void *` context pointer should be updated to take a `drawing *`
pointer in its place. Then, where each such function would have
previously casted the `void *` pointer to a meaningful type, they now
instead retrieve the context pointer from the `handle` field of the
`drawing` structure. To make this transition easier, the
`GET_HANDLE_AS_TYPE()` macro is introduced to wrap the context pointer
retrieval (see below for usage).

As an example, an old drawing API function implementation would have
looked like this:

void frontend_draw_func(void *handle, ...)
{
    frontend *fe = (frontend *)handle;
    /* do stuff with fe */
}

After this change, that function would be rewritten as:

void frontend_draw_func(drawing *dr, ...)
{
    frontend *fe = GET_HANDLE_AS_TYPE(dr, frontend);
    /* do stuff with fe */
}

I have already made these changes to all the in-tree front ends, but
out-of-tree front ends will need to follow the procedure outlined
above.

Simon pointed out that changing the drawing API function pointer
signatures to take `drawing *` instead of `void *` results only in a
compiler warning, not an outright error. Thus, I've introduced a
version field to the beginning of the `drawing_api` struct, which will
cause a compilation error and hopefully force front ends to notice
this. This field should be set to 1 for now. Going forward, it will
provide a clear means of communicating future breaking API changes.
This commit is contained in:
Franklin Wei
2024-08-11 19:28:35 -04:00
committed by Simon Tatham
parent a993fd45eb
commit f37913002a
11 changed files with 459 additions and 342 deletions

128
devel.but
View File

@ -1975,18 +1975,19 @@ In fact these global functions are not implemented directly by the
front end; instead, they are implemented centrally in \c{drawing.c}
and form a small piece of middleware. The drawing API as supplied by
the front end is a structure containing a set of function pointers,
plus a \cq{void *} handle which is passed to each of those
functions. This enables a single front end to switch between
multiple implementations of the drawing API if necessary. For
example, the Windows API supplies a printing mechanism integrated
into the same GDI which deals with drawing in windows, and therefore
the same API implementation can handle both drawing and printing;
but on Unix, the most common way for applications to print is by
producing PostScript output directly, and although it would be
\e{possible} to write a single (say) \cw{draw_rect()} function which
checked a global flag to decide whether to do GTK drawing operations
or output PostScript to a file, it's much nicer to have two separate
functions and switch between them as appropriate.
plus a \cq{void *} handle which is indirectly passed to each of those
functions as a member of the same drawing object as used by the
backend. This enables a single front end to switch between multiple
implementations of the drawing API if necessary. For example, the
Windows API supplies a printing mechanism integrated into the same GDI
which deals with drawing in windows, and therefore the same API
implementation can handle both drawing and printing; but on Unix, the
most common way for applications to print is by producing PostScript
output directly, and although it would be \e{possible} to write a
single (say) \cw{draw_rect()} function which checked a global flag to
decide whether to do GTK drawing operations or output PostScript to a
file, it's much nicer to have two separate functions and switch
between them as appropriate.
When drawing, the puzzle window is indexed by pixel coordinates,
with the top left pixel defined as \cw{(0,0)} and the bottom right
@ -2689,17 +2690,38 @@ API; the platform-independent module \c{ps.c} also provides an
implementation of it which outputs PostScript. Thus, any platform
which wants to do PS printing can do so with minimum fuss.)
The following entries all describe function pointer fields in a
structure called \c{drawing_api}. Each of the functions takes a
\cq{void *} context pointer, which it should internally cast back to
a more useful type. Thus, a drawing \e{object} (\c{drawing *)}
suitable for passing to the back end redraw or printing functions
is constructed by passing a \c{drawing_api} and a \cq{void *} to the
function \cw{drawing_new()} (see \k{drawing-new}).
The following entries (with the exception of the \cw{version} field)
all describe function pointer fields in a structure called
\c{drawing_api}. Each of the functions takes a \cq{drawing *} pointer,
which in turn contains a \cq{void *handle} member that the front end
can access and internally cast back to a more useful type. Since this
is a fairly common thing for front ends to do, the
\cw{GET_HANDLE_AS_TYPE()} macro is provided to do this for you (see
\k{utils-get-handle-as-type}).
A drawing \e{object} (\c{drawing *)} suitable for passing to the back
end redraw or printing functions is constructed by passing a
\c{drawing_api} and a \cq{void *} to the function \cw{drawing_new()}
(see \k{drawing-new}).
\S{drawingapi-version} \cw{version}
\c int version;
This is an integer giving the version of the drawing API implemented
by the front end. For the version of the API described in this
document, this field should be \cw{1}.
On occasion, there may be a breaking change to the drawing API, such
as the introduction of a new function or a change to the semantics of
an existing function. The intent of this version field is to signal to
front end authors when such a breaking change occurs. As such changes
can be disruptive to downstream front ends, they should be done
sparingly.
\S{drawingapi-draw-text} \cw{draw_text()}
\c void (*draw_text)(void *handle, int x, int y, int fonttype,
\c void (*draw_text)(drawing *dr, int x, int y, int fonttype,
\c int fontsize, int align, int colour,
\c const char *text);
@ -2708,7 +2730,7 @@ function; see \k{drawing-draw-text}.
\S{drawingapi-draw-rect} \cw{draw_rect()}
\c void (*draw_rect)(void *handle, int x, int y, int w, int h,
\c void (*draw_rect)(drawing *dr, int x, int y, int w, int h,
\c int colour);
This function behaves exactly like the back end \cw{draw_rect()}
@ -2716,7 +2738,7 @@ function; see \k{drawing-draw-rect}.
\S{drawingapi-draw-line} \cw{draw_line()}
\c void (*draw_line)(void *handle, int x1, int y1, int x2, int y2,
\c void (*draw_line)(drawing *dr, int x1, int y1, int x2, int y2,
\c int colour);
This function behaves exactly like the back end \cw{draw_line()}
@ -2724,7 +2746,7 @@ function; see \k{drawing-draw-line}.
\S{drawingapi-draw-polygon} \cw{draw_polygon()}
\c void (*draw_polygon)(void *handle, const int *coords, int npoints,
\c void (*draw_polygon)(drawing *dr, const int *coords, int npoints,
\c int fillcolour, int outlinecolour);
This function behaves exactly like the back end \cw{draw_polygon()}
@ -2732,7 +2754,7 @@ function; see \k{drawing-draw-polygon}.
\S{drawingapi-draw-circle} \cw{draw_circle()}
\c void (*draw_circle)(void *handle, int cx, int cy, int radius,
\c void (*draw_circle)(drawing *dr, int cx, int cy, int radius,
\c int fillcolour, int outlinecolour);
This function behaves exactly like the back end \cw{draw_circle()}
@ -2754,7 +2776,7 @@ and provide a low-quality alternative using \cw{draw_polygon()}.
\S{drawingapi-draw-update} \cw{draw_update()}
\c void (*draw_update)(void *handle, int x, int y, int w, int h);
\c void (*draw_update)(drawing *dr, int x, int y, int w, int h);
This function behaves exactly like the back end \cw{draw_update()}
function; see \k{drawing-draw-update}.
@ -2766,21 +2788,21 @@ than bothering to define an empty function. The middleware in
\S{drawingapi-clip} \cw{clip()}
\c void (*clip)(void *handle, int x, int y, int w, int h);
\c void (*clip)(drawing *dr, int x, int y, int w, int h);
This function behaves exactly like the back end \cw{clip()}
function; see \k{drawing-clip}.
\S{drawingapi-unclip} \cw{unclip()}
\c void (*unclip)(void *handle);
\c void (*unclip)(drawing *dr);
This function behaves exactly like the back end \cw{unclip()}
function; see \k{drawing-unclip}.
\S{drawingapi-start-draw} \cw{start_draw()}
\c void (*start_draw)(void *handle);
\c void (*start_draw)(drawing *dr);
This function is called at the start of drawing. It allows the front
end to initialise any temporary data required to draw with, such as
@ -2792,7 +2814,7 @@ called unless drawing is attempted.
\S{drawingapi-end-draw} \cw{end_draw()}
\c void (*end_draw)(void *handle);
\c void (*end_draw)(drawing *dr);
This function is called at the end of drawing. It allows the front
end to do cleanup tasks such as deallocating device contexts and
@ -2804,7 +2826,7 @@ called unless drawing is attempted.
\S{drawingapi-status-bar} \cw{status_bar()}
\c void (*status_bar)(void *handle, const char *text);
\c void (*status_bar)(drawing *dr, const char *text);
This function behaves exactly like the back end \cw{status_bar()}
function; see \k{drawing-status-bar}.
@ -2819,7 +2841,7 @@ called unless drawing is attempted.
\S{drawingapi-blitter-new} \cw{blitter_new()}
\c blitter *(*blitter_new)(void *handle, int w, int h);
\c blitter *(*blitter_new)(drawing *dr, int w, int h);
This function behaves exactly like the back end \cw{blitter_new()}
function; see \k{drawing-blitter-new}.
@ -2830,7 +2852,7 @@ called unless drawing is attempted.
\S{drawingapi-blitter-free} \cw{blitter_free()}
\c void (*blitter_free)(void *handle, blitter *bl);
\c void (*blitter_free)(drawing *dr, blitter *bl);
This function behaves exactly like the back end \cw{blitter_free()}
function; see \k{drawing-blitter-free}.
@ -2841,7 +2863,7 @@ called unless drawing is attempted.
\S{drawingapi-blitter-save} \cw{blitter_save()}
\c void (*blitter_save)(void *handle, blitter *bl, int x, int y);
\c void (*blitter_save)(drawing *dr, blitter *bl, int x, int y);
This function behaves exactly like the back end \cw{blitter_save()}
function; see \k{drawing-blitter-save}.
@ -2852,7 +2874,7 @@ called unless drawing is attempted.
\S{drawingapi-blitter-load} \cw{blitter_load()}
\c void (*blitter_load)(void *handle, blitter *bl, int x, int y);
\c void (*blitter_load)(drawing *dr, blitter *bl, int x, int y);
This function behaves exactly like the back end \cw{blitter_load()}
function; see \k{drawing-blitter-load}.
@ -2863,7 +2885,7 @@ called unless drawing is attempted.
\S{drawingapi-begin-doc} \cw{begin_doc()}
\c void (*begin_doc)(void *handle, int pages);
\c void (*begin_doc)(drawing *dr, int pages);
This function is called at the beginning of a printing run. It gives
the front end an opportunity to initialise any required printing
@ -2875,7 +2897,7 @@ called unless printing is attempted.
\S{drawingapi-begin-page} \cw{begin_page()}
\c void (*begin_page)(void *handle, int number);
\c void (*begin_page)(drawing *dr, int number);
This function is called during printing, at the beginning of each
page. It gives the page number (numbered from 1 rather than 0, so
@ -2887,7 +2909,7 @@ called unless printing is attempted.
\S{drawingapi-begin-puzzle} \cw{begin_puzzle()}
\c void (*begin_puzzle)(void *handle, float xm, float xc,
\c void (*begin_puzzle)(drawing *dr, float xm, float xc,
\c float ym, float yc, int pw, int ph, float wmm);
This function is called during printing, just before printing a
@ -2930,7 +2952,7 @@ called unless printing is attempted.
\S{drawingapi-end-puzzle} \cw{end_puzzle()}
\c void (*end_puzzle)(void *handle);
\c void (*end_puzzle)(drawing *dr);
This function is called after the printing of a specific puzzle is
complete.
@ -2941,7 +2963,7 @@ called unless printing is attempted.
\S{drawingapi-end-page} \cw{end_page()}
\c void (*end_page)(void *handle, int number);
\c void (*end_page)(drawing *dr, int number);
This function is called after the printing of a page is finished.
@ -2951,7 +2973,7 @@ called unless printing is attempted.
\S{drawingapi-end-doc} \cw{end_doc()}
\c void (*end_doc)(void *handle);
\c void (*end_doc)(drawing *dr);
This function is called after the printing of the entire document is
finished. This is the moment to close files, send things to the
@ -2963,7 +2985,7 @@ called unless printing is attempted.
\S{drawingapi-line-width} \cw{line_width()}
\c void (*line_width)(void *handle, float width);
\c void (*line_width)(drawing *dr, float width);
This function is called to set the line thickness, during printing
only. Note that the width is a \cw{float} here, where it was an
@ -2979,7 +3001,7 @@ called unless printing is attempted.
\S{drawingapi-line-dotted} \cw{line_dotted()}
\c void (*line_dotted)(void *handle, bool dotted);
\c void (*line_dotted)(drawing *dr, bool dotted);
This function is called to toggle drawing of dotted lines, during
printing only.
@ -2990,7 +3012,7 @@ called unless printing is attempted.
\S{drawingapi-text-fallback} \cw{text_fallback()}
\c char *(*text_fallback)(void *handle, const char *const *strings,
\c char *(*text_fallback)(drawing *dr, const char *const *strings,
\c int nstrings);
This function behaves exactly like the back end \cw{text_fallback()}
@ -3013,9 +3035,10 @@ implement. They are described in this section.
\c void *handle);
This function creates a drawing object. It is passed a
\c{drawing_api}, which is a structure containing nothing but
function pointers; and also a \cq{void *} handle. The handle is
passed back to each function pointer when it is called.
\c{drawing_api}, which is a structure containing function pointers and
a version field; and also a \cq{void *} handle. The handle is stored
in the drawing object, from where it may be accessed by each function
pointer when it is called.
The \c{midend} parameter is used for rewriting the status bar
contents: \cw{status_bar()} (see \k{drawing-status-bar}) has to call
@ -3024,6 +3047,10 @@ If the drawing object is to be used only for printing, or if the
game is known not to call \cw{status_bar()}, this parameter may be
\cw{NULL}.
A fatal error is produced if the \cw{version} field of the
\c{drawing_api} does not match the expected version (see
\k{drawingapi-version} for the expected version number).
\S{drawing-free} \cw{drawing_free()}
\c void drawing_free(drawing *dr);
@ -5351,6 +5378,15 @@ argument is respectively less than, equal to, or greater than the
second. This function is intended to be passed to \c{qsort()} for
sorting ints in ascending order.
\S{utils-get-handle-as-type} \cw{GET_HANDLE_AS_TYPE()}
\c #define GET_HANDLE_AS_TYPE(dr, type) ((type*)((dr)->handle))
This macro, defined in the main Puzzles header file, retrieves the
\c{handle} field from a \c{drawing *} pointer and casts it to \c{type
*}. It is intended for use in implementations of drawing API
functions, where this operation is idiomatic.
\C{writing} How to write a new puzzle
This chapter gives a guide to how to actually write a new puzzle: