## pen-cache.pkg
#
# Track graphics-contexts in the X server.
#
# Reppy had this as a full-fledged imp
#
#
src/lib/x-kit/xclient/src/window/pen-to-gcontext-imp-old.pkg#
# but it wound up only used by
#
#
src/lib/x-kit/xclient/src/window/xserver-ximp.pkg#
# so it got demoted to a support package. It is used only by a
# single xserver-ximp microthread, so we have no concurrency issues.
# -- 2013-07-17 CrT
#
# For the big picture see the imp dataflow diagrams in
#
#
src/lib/x-kit/xclient/src/window/xclient-ximps.pkg#
# NB: Throughout this file, "gc" == "(X11) graphics context"
# -- NOT "garbage collector"!
# Compiled by:
#
src/lib/x-kit/xclient/xclient-internals.sublibstipulate
include package threadkit; # threadkit is from
src/lib/src/lib/thread-kit/src/core-thread-kit/threadkit.pkg #
#
package un = unt; # unt is from
src/lib/std/unt.pkg package rwv = rw_vector; # rw_vector is from
src/lib/std/src/rw-vector.pkg package vec = vector; # vector is from
src/lib/std/src/vector.pkg package v1u = vector_of_one_byte_unts; # vector_of_one_byte_unts is from
src/lib/std/src/vector-of-one-byte-unts.pkg package v2w = value_to_wire; # value_to_wire is from
src/lib/x-kit/xclient/src/wire/value-to-wire.pkg package w2v = wire_to_value; # wire_to_value is from
src/lib/x-kit/xclient/src/wire/wire-to-value.pkg package g2d = geometry2d; # geometry2d is from
src/lib/std/2d/geometry2d.pkg package xtr = xlogger; # xlogger is from
src/lib/x-kit/xclient/src/stuff/xlogger.pkg package pg = pen_guts; # pen_guts is from
src/lib/x-kit/xclient/src/window/pen-guts.pkg package xt = xtypes; # xtypes is from
src/lib/x-kit/xclient/src/wire/xtypes.pkg #
trace = xtr::log_if xtr::io_logging 0; # Conditionally write strings to tracing.log or whatever.
herein
package pen_cache
: (weak) Pen_Cache # Pen_Cache is from
src/lib/x-kit/xclient/src/window/pen-cache.api {
gc_slot_count = 23;
font_gcslot = 14; # The slot in a GC for the font.
clip_origin_penslot = 14; # The slot in a pen for the clip origin.
clip_mask_penslot = 15; # The slot in a pen for the clip mask.
dash_offset_penslot = 16; # The slot in a pen for the dash offset.
dashlist_penslot = 17; # The slot in a pen for the dash list.
min_hit_rate = 80; # We want at least 80% of GC requests to be matched.
# GC request/reply messages. # "GC" == "graphics context" throughout this file.
#
# There are two basic requests: acquire and release a GC.
# When acquiring a GC, one supplies a pen
# and bit-vector telling which fields are
# used by the drawing operation.
#
# For text drawing, there are two
# forms of acquire request:
#
# ACQUIRE_GC_WITH_FONT specifies that
# the font field is needed; the reply will be
# REPLY_GC_WITH_FONT and will specify the
# current value of the GC's font. It is the
# drawing operation's (presumably a DrawText)
# responsibility to restore the font.
#
# ACQUIRE_GC_AND_SET_FONT request requires
# that the GC have the requested font and
# will generate a normal REPLY_GC reply.
#
# A given graphics context may have
# no associated font. If it does have
# an associated font, that font may be
# in use or unused:
#
Font_Status
#
= NO_FONT # No font has been set yet in this GC.
| UNUSED_FONT xt::Font_Id
# There is a font set, but it is not currently being used.
| IN_USE_FONT (xt::Font_Id, Int)
# In-use font plus current number of users.
;
Free_Gc
=
FREE_GC
{ gc_id: xt::Graphics_Context_Id, # 29-bit integer X id for X-server graphics context.
pen: pg::Pen, # Describes values of the GC.
font: Font_Status # The current font (if any).
};
In_Use_Gc
=
IN_USE_GC
{ gc_id: xt::Graphics_Context_Id, # 29-bit integer X id for X-server graphics context.
pen: pg::Pen, # Describes values of the GC.
font: Ref( Font_Status ), # The current font (if any).
used_mask: Ref( Unt ), # A bit-mask telling which components of the GC are being used.
refcount: Ref( Int ) # The number of draw_imp clients using the GC, including those using the font.
};
Pen_Cache # All nonephemeral pen-cache state.
=
{ hits: Ref(Int),
misses: Ref(Int),
in_use_gcs: Ref( List(In_Use_Gc) ),
free_gcs: Ref( List( Free_Gc) ),
#
drawable: xt::Drawable_Id,
next_xid: Void -> xt::Xid, # resource id allocator. Implemented by spawn_xid_factory_thread() from
src/lib/x-kit/xclient/src/wire/display-old.pkg default_gcid: xt::Graphics_Context_Id
};
fun font_sts2s (NO_FONT) => "NoFont";
font_sts2s (UNUSED_FONT f) => string::cat ["UNUSED_FONT(", xt::xid_to_string f, ")"];
font_sts2s (IN_USE_FONT (f, n)) => string::cat [ "IN_USE_FONT(", xt::xid_to_string f, ", ", int::to_string n, ")" ];
end;
#
fun in_use_gc_to_string (IN_USE_GC { gc_id, pen, font, used_mask, refcount } )
=
string::cat
[
"IN_USE_GC { gc_id=", xt::xid_to_string gc_id, ", font=", font_sts2s *font,
", refcount=", int::to_string *refcount, "}"
];
# Search a list of in-use GCs for
# given gc_id and remove if free.
#
# We return NULL if gc did not become free,
# otherwise the new FREE_GC plus the input
# list with it removed:
#
fun find_in_use_gc (our_gc_id, font_used, in_use_gcs)
=
find in_use_gcs
where
fun find []
=>
xgripe::impossible "[pen_to_gcontext_imp: lost in-use graphics context]";
find ((x as IN_USE_GC { gc_id, ... } ) ! rest)
=>
if (gc_id != our_gc_id)
#
case (find rest)
#
THE (free_gcs, l) => THE (free_gcs, x ! l);
NULL => NULL;
esac;
else
case (font_used, x)
#
(FALSE, IN_USE_GC { refcount => REF 1, pen, font, ... })
=>
THE (FREE_GC { gc_id, pen, font => *font }, rest); # Removing last reference makes GC free.
(TRUE, IN_USE_GC { refcount => REF 1, pen, font => REF (IN_USE_FONT (f, 1)), ... })
=>
THE (FREE_GC { gc_id, pen, font => UNUSED_FONT f }, rest); # Ditto plus marking font as unused.
(FALSE, IN_USE_GC { refcount as REF n, ... })
=>
{ refcount := n - 1;
NULL;
};
(TRUE, IN_USE_GC { refcount as REF n, font as REF (IN_USE_FONT (f, 1)), ... })
=>
{ refcount := n - 1;
font := (UNUSED_FONT f);
NULL;
};
(TRUE, IN_USE_GC { refcount as REF n, font as REF (IN_USE_FONT (f, nf)), ... })
=>
{ refcount := n - 1;
font := IN_USE_FONT (f, nf - 1);
NULL;
};
(_, gc)
=>
xgripe::impossible (string::cat [
"[Pen_Imp::findUsedGC: bogus in-use GC; font_used = ",
bool::to_string font_used, ", gc = ", in_use_gc_to_string gc, "]"
]);
esac;
fi;
end;
end;
my (penslot_to_gcmask, penslot_to_gcslot)
=
{
l = [
[0], # pen-slot 0: function
[1], # pen-slot 1: plane mask
[2], # pen-slot 2: foreground
[3], # pen-slot 3: background
[4], # pen-slot 4: line-width
[5], # pen-slot 5: line-style
[6], # pen-slot 6: cap-style
[7], # pen-slot 7: join-style
[8], # pen-slot 8: fill-style
[9], # pen-slot 9: fill-rule
[10], # pen-slot 10: tile
[11], # pen-slot 11: stipple
[12, 13], # pen-slot 12: tile/stipple origin
[15], # pen-slot 13: subwindow mode
[17, 18], # pen-slot 14: clipping origin
[19], # pen-slot 15: clipping mask
[20], # pen-slot 16: dash offset
[21], # pen-slot 17: dash list
[22] # pen-slot 18: arc mode
];
# Convert [12, 13] to an unt
# with bits 12, 13 set to 1, etc:
#
fun bitmask [] => 0u0;
bitmask (i ! r) => (0u1 << unt::from_int i)
| (bitmask r);
end;
(vec::from_list (map bitmask l), vec::from_list (map head l));
};
#
fun pen_mask_to_gcmask pen_mask
=
loop (pen_mask, 0, 0u0)
where
fun loop (0u0, _, m)
=>
m;
loop (mask, i, m)
=>
(mask & 0u1) == 0u0
##
?? loop (mask >> 0u1, i+1, m )
:: loop (mask >> 0u1, i+1, m
| penslot_to_gcmask[i]);
end;
end;
#
fun hit_rate (hits, misses)
=
{ total = hits + misses;
if (total == 0) 100;
else int::quot((100 * hits), total);
fi;
};
# Map the values of a pen to an X-server
# GC initialization rw_vector.
#
# "dst_mask" specifies which values
# in the pen are to be mapped.
#
# Assume that all values are non-default:
# we copy fields from the screen's
# default GC for those.
#
fun pen_to_gcvals ({ traits, ... }: pg::Pen, dst_mask, font)
=
{ gc_vals = rwv::make_rw_vector (gc_slot_count, NULL);
#
fun update (i, v) = gc_vals[i] := THE (unt::from_int v);
fun update_u (i, v) = gc_vals[i] := THE v;
#
fun init_val (i, pg::IS_WIRE v)
=>
update_u (penslot_to_gcslot[i], v);
init_val (i, pg::IS_POINT ({ col, row } ))
=>
{ j = penslot_to_gcslot[i];
update (j, col);
update (j+1, row);
};
init_val (i, pg::IS_PIXMAP xid)
=>
update_u (penslot_to_gcslot[i], xt::xid_to_unt xid);
init_val _
=>
();
end;
#
fun init_vals (0u0, _)
=>
();
init_vals (m, i)
=>
{ if ((m & 0u1) != 0u0)
#
init_val (i, traits[i]);
fi;
init_vals (m >> 0u1, i+1);
};
end;
case font
#
THE font_id => update_u (font_gcslot, xt::xid_to_unt font_id);
NULL => ();
esac;
init_vals (dst_mask, 0);
{ vals => xt::VALUE_LIST gc_vals,
#
clip_boxes
=>
if ((dst_mask & (0u1 << unt::from_int clip_mask_penslot)) == 0u0)
#
NULL;
else
case (traits[clip_mask_penslot])
#
pg::IS_BOXES boxes
=>
(THE (traits[ clip_origin_penslot ], boxes));
_ => NULL;
esac;
fi,
dashes => if ((dst_mask & (0u1 << unt::from_int dashlist_penslot)) == 0u0)
#
NULL;
else
case (traits[ dashlist_penslot ])
#
pg::IS_DASHES dashes
=>
THE (traits[ dash_offset_penslot ], dashes);
_ => NULL;
esac;
fi
};
}; # fun pen_to_gcvals
#
fun set_dashes (_, NULL, note_xrequest)
=>
();
set_dashes (gc_id, THE (pg::IS_WIRE offset, dashes), note_xrequest)
=>
note_xrequest (v2w::encode_set_dashes { gc_id, dashes, dash_offset => unt::to_int_x offset });
set_dashes (gc_id, THE(_, dashes), note_xrequest)
=>
note_xrequest (v2w::encode_set_dashes { gc_id, dashes, dash_offset => 0 });
end;
#
fun set_clip_boxes (_, NULL, note_xrequest)
=>
();
set_clip_boxes (gc_id, THE (pg::IS_POINT pt, (order, boxes)), note_xrequest)
=>
note_xrequest (v2w::encode_set_clip_boxes { gc_id, boxes, clip_origin => pt, ordering => order });
set_clip_boxes (gc_id, THE(_, (order, boxes)), note_xrequest)
=>
note_xrequest (v2w::encode_set_clip_boxes { gc_id, clip_origin => g2d::point::zero, ordering => order, boxes });
end;
# Set the font of a GC:
#
fun set_font (gc_id, font_id, note_xrequest)
=
{ vals = rwv::make_rw_vector (gc_slot_count, NULL);
#
vals[font_gcslot] := THE (xt::xid_to_unt font_id);
note_xrequest (v2w::encode_change_gc { gc_id, vals => xt::VALUE_LIST vals });
};
# Create a new X-server graphics context.
# It is in-use by definition:
#
fun make_gc (pen as { bitmask, ... }: pg::Pen, used_mask, font, drawable, next_xid, note_xrequest)
=
{ (pen_to_gcvals (pen, bitmask, font))
->
{ vals, dashes, clip_boxes };
gc_id = next_xid();
note_xrequest (v2w::encode_create_gc { gc_id, drawable, vals });
set_dashes (gc_id, dashes, note_xrequest);
set_clip_boxes (gc_id, clip_boxes, note_xrequest);
IN_USE_GC { gc_id,
pen,
refcount => REF 1,
#
used_mask => REF used_mask,
font => REF case font NULL => NO_FONT;
THE f => IN_USE_FONT (f, 1);
esac
};
};
# Update an X-server GC so that
# it agrees with the given pen
# on the used values:
#
fun change_gc
(
FREE_GC { gc_id, font=>cur_font, ... },
pen as { bitmask, ... }: pg::Pen,
used_mask,
new_font,
default_gcid,
note_xrequest
)
=
{ non_default_mask = bitmask & used_mask;
#
default_mask = (unt::bitwise_not bitmask) & used_mask;
my (different_font, font)
=
case (cur_font, new_font)
#
(_, NULL ) => (FALSE, NO_FONT);
(NO_FONT, THE font_id ) => (TRUE, IN_USE_FONT (font_id, 1));
(UNUSED_FONT font_id1, THE font_id2) => ((font_id1 != font_id2), IN_USE_FONT (font_id2, 1));
(IN_USE_FONT _, _ ) => xgripe::impossible "[Pen_Imp: used font in free_gcs gc]";
esac;
if (default_mask != 0u0)
#
note_xrequest ( v2w::encode_copy_gc
{ from => default_gcid,
to => gc_id,
mask => xt::VALUE_MASK (pen_mask_to_gcmask default_mask)
}
);
fi;
if (non_default_mask != 0u0
or different_font)
(pen_to_gcvals (pen, bitmask, new_font))
->
{ vals, dashes, clip_boxes };
note_xrequest (v2w::encode_change_gc { gc_id, vals });
set_dashes (gc_id, dashes, note_xrequest);
set_clip_boxes (gc_id, clip_boxes, note_xrequest);
fi;
IN_USE_GC { gc_id,
pen,
font => REF font,
used_mask => REF used_mask,
refcount => REF 1
};
};
# Search a list of in-use GCs for
# one that matches the given pen:
#
fun match_in_use_gc (pen, used_mask, font, in_use_gcs, note_xrequest)
=
f in_use_gcs
where
# NOTE: there may be used components in pen that are not used in arg, but that
# are defined differently. We could still use arg, but we'll have to update it.
# The test for an approx. match would be:
# if (pg::pen_match (m & used_mask, pen, pen')
#
match = case font
#
NULL => (\\ (IN_USE_GC { pen => pen', ... } )
=
pg::pen_match (used_mask, pen, pen')
);
THE f => match
where
fun match (IN_USE_GC { pen => pen', font => REF (IN_USE_FONT (f', _)), ... } )
=>
( f == f'
and pg::pen_match (used_mask, pen, pen')
);
match (IN_USE_GC { pen => pen', ... } )
=>
pg::pen_match (used_mask, pen, pen');
end;
end;
esac;
#
fun f [] => NULL;
#
f (arg ! r)
=>
if (match arg)
#
arg -> IN_USE_GC { refcount, used_mask => used_mask', ... };
#
refcount := *refcount + 1;
used_mask' := (*used_mask'
| used_mask);
THE arg;
else
f r;
fi;
end;
end;
# Search the list of free graphics contexts for a match.
#
# If none is found, then take the last one and
# modify it to work. If the list is empty,
# then create a new graphics context.
#
fun match_free_gc (hits, misses, pen, used_mask, font, free_gcs, drawable, next_xid, default_gcid, note_xrequest)
=
match_free_gc' (free_gcs, [])
where
# Reverse first arg and prepend it to second arg:
#
fun reverse_and_prepend ([], l) => l;
reverse_and_prepend (x ! r, l) => reverse_and_prepend (r, x ! l);
end;
my (match, make_used)
=
case font
#
NULL => (match, make_used)
where
fun match (FREE_GC { pen => pen', ... } )
=
pg::pen_match (used_mask, pen, pen');
#
fun make_used (FREE_GC { gc_id, pen, font } )
=
IN_USE_GC { gc_id,
pen,
font => REF font,
used_mask => REF used_mask,
refcount => REF 1
};
end;
THE font_id => (match, make_used)
where
#
fun match (FREE_GC { font => NO_FONT, ... } )
=>
FALSE;
match (FREE_GC { pen => pen', font => UNUSED_FONT f, ... } )
=>
f == font_id
and
pg::pen_match (used_mask, pen, pen');
match (FREE_GC { font => (IN_USE_FONT _), ... } )
=>
xgripe::impossible "[Pen_Imp: used font in avail gc]";
end;
#
fun make_used (FREE_GC { gc_id, pen, ... } )
=
IN_USE_GC { gc_id,
pen,
font => REF (IN_USE_FONT (font_id, 1)),
used_mask => REF used_mask,
refcount => REF 1
};
end;
esac;
#
fun match_free_gc' ([], l)
=>
{ in_use_gc => make_gc (pen, used_mask, font, drawable, next_xid, note_xrequest),
free_gcs => reverse_and_prepend (l, []),
hits => 0,
misses => 0
};
match_free_gc' ([last as FREE_GC _ ], l)
=>
if (match last)
#
{ in_use_gc => make_used last,
free_gcs => reverse_and_prepend (l, []),
hits => hits+1,
misses
};
else
if (hit_rate (hits, misses) < min_hit_rate)
#
{ in_use_gc => make_gc (pen, used_mask, font, drawable, next_xid, note_xrequest),
free_gcs => reverse_and_prepend (l, [last]),
hits => 0,
misses => 0
};
else
{ in_use_gc => change_gc (last, pen, used_mask, font, default_gcid, note_xrequest),
free_gcs => reverse_and_prepend (l, []),
hits,
misses => misses+1
};
fi;
fi;
match_free_gc' (x ! r, l)
=>
if (match x)
#
{ in_use_gc => make_used x,
free_gcs => reverse_and_prepend (l, r),
hits => hits+1,
misses
};
else
match_free_gc' (r, x ! l);
fi;
end;
end;
##########################################################################################
# PUBLIC.
#
#
fun allocate_graphics_context (me: Pen_Cache) # PUBLIC.
{
pen: pg::Pen,
used_mask: Unt,
note_xrequest: v1u::Vector -> Void
}
=
case (match_in_use_gc (pen, used_mask, NULL, *me.in_use_gcs, note_xrequest))
#
THE (IN_USE_GC { gc_id, ... } )
=>
{ me.hits := *me.hits + 1;
#
gc_id;
};
NULL
=>
{ (match_free_gc (*me.hits, *me.misses, pen, used_mask, NULL, *me.free_gcs, me.drawable, me.next_xid, me.default_gcid, note_xrequest))
->
{ in_use_gc as IN_USE_GC { gc_id, ... }, hits, misses, free_gcs };
me.hits := hits;
me.misses := misses;
me.in_use_gcs := in_use_gc ! *me.in_use_gcs;
me.free_gcs := free_gcs;
gc_id;
};
esac;
#
fun allocate_graphics_context_with_font (me: Pen_Cache) # PUBLIC.
{ pen: pg::Pen,
used_mask: Unt,
note_xrequest: v1u::Vector -> Void,
font_id: xt::Font_Id
}
=
case (match_in_use_gc (pen, used_mask, NULL, *me.in_use_gcs, note_xrequest))
#
THE (IN_USE_GC { gc_id, font as (REF NO_FONT), ... } )
=>
{ set_font (gc_id, font_id, note_xrequest);
#
font := IN_USE_FONT (font_id, 1);
me.hits := *me.hits + 1;
{ gc_id, font_id };
};
THE (IN_USE_GC { gc_id, font as (REF (UNUSED_FONT f)), ... } )
=>
{ if (f != font_id)
set_font (gc_id, font_id, note_xrequest);
font := IN_USE_FONT (font_id, 1);
else font := IN_USE_FONT (font_id, 1);
fi;
me.hits := *me.hits + 1;
{ gc_id, font_id };
};
THE (IN_USE_GC { gc_id, font as (REF (IN_USE_FONT (f, n))), ... } )
=>
{ font := IN_USE_FONT (f, n+1);
#
me.hits := *me.hits + 1;
{ gc_id, font_id => f };
};
NULL
=>
{ (match_free_gc (*me.hits, *me.misses, pen, used_mask, THE font_id, *me.free_gcs, me.drawable, me.next_xid, me.default_gcid, note_xrequest))
->
{ in_use_gc as IN_USE_GC { gc_id, ... }, hits, misses, free_gcs };
me.hits := hits;
me.misses := misses;
me.in_use_gcs := in_use_gc ! *me.in_use_gcs;
me.free_gcs := free_gcs;
{ gc_id, font_id };
};
esac;
#
fun allocate_graphics_context_and_set_font (me: Pen_Cache) # PUBLIC.
{
pen: pg::Pen,
used_mask: Unt,
note_xrequest: v1u::Vector -> Void,
font_id: xt::Font_Id
}
=
case (match_in_use_gc (pen, used_mask, THE font_id, *me.in_use_gcs, note_xrequest))
#
THE (IN_USE_GC { gc_id, font as (REF NO_FONT), ... } )
=>
{ set_font (gc_id, font_id, note_xrequest);
#
font := IN_USE_FONT (font_id, 1);
me.hits := *me.hits + 1;
gc_id;
};
THE (IN_USE_GC { gc_id, font as (REF (UNUSED_FONT f)), ... } )
=>
{ if (f != font_id)
set_font (gc_id, font_id, note_xrequest);
fi;
font := IN_USE_FONT (font_id, 1);
me.hits := *me.hits + 1;
gc_id;
};
THE (IN_USE_GC { gc_id, font as (REF (IN_USE_FONT (f, n))), ... } )
=>
{ font := IN_USE_FONT (f, n+1); # NOTE: f = fId!
#
me.hits := *me.hits + 1;
gc_id;
};
NULL =>
{ (match_free_gc (*me.hits, *me.misses, pen, used_mask, THE font_id, *me.free_gcs, me.drawable, me.next_xid, me.default_gcid, note_xrequest))
->
{ in_use_gc as IN_USE_GC { gc_id, ... }, hits, misses, free_gcs };
me.hits := hits;
me.misses := misses;
me.in_use_gcs := in_use_gc ! *me.in_use_gcs;
me.free_gcs := free_gcs;
gc_id;
};
esac;
#
fun free_graphics_context (me: Pen_Cache) (id: xt::Graphics_Context_Id) # PUBLIC.
=
case (find_in_use_gc (id, FALSE, *me.in_use_gcs))
#
THE (x, l) => { me.in_use_gcs := l;
me.free_gcs := x ! *me.free_gcs;
};
NULL => {
};
esac;
#
fun free_graphics_context_and_font (me: Pen_Cache) (id: xt::Graphics_Context_Id) # PUBLIC.
=
case (find_in_use_gc (id, TRUE, *me.in_use_gcs))
#
THE (x, l) => { me.in_use_gcs := l;
me.free_gcs := x ! *me.free_gcs;
};
NULL => {
};
esac;
#
fun make_pen_cache # PUBLIC.
{
drawable: xt::Drawable_Id,
next_xid: Void -> xt::Xid, # resource id allocator. Implemented by spawn_xid_factory_thread() from
src/lib/x-kit/xclient/src/wire/display-old.pkg note_xrequest: v1u::Vector -> Void
}
=
{
#
(make_gc (pg::default_pen, 0ux7FFFFF, NULL, drawable, next_xid, note_xrequest))
->
IN_USE_GC { gc_id => default_gcid, ... };
{ hits => REF 0,
misses => REF 0,
#
in_use_gcs => REF ([]: List(In_Use_Gc)),
free_gcs => REF ([]: List( Free_Gc)),
#
drawable,
next_xid,
default_gcid
};
};
}; # package pen_cache
end;