@gtkx/runtime 1.0.0-rc.3 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/README.md +8 -9
  2. package/dist/application-class.d.ts +32 -0
  3. package/dist/application-class.d.ts.map +1 -0
  4. package/dist/application-class.js +68 -0
  5. package/dist/application-class.js.map +1 -0
  6. package/dist/arg.d.ts +9 -3
  7. package/dist/arg.d.ts.map +1 -1
  8. package/dist/arg.js +4 -3
  9. package/dist/arg.js.map +1 -1
  10. package/dist/bind.d.ts +11 -0
  11. package/dist/bind.d.ts.map +1 -1
  12. package/dist/bind.js +11 -0
  13. package/dist/bind.js.map +1 -1
  14. package/dist/callback.d.ts +2 -1
  15. package/dist/callback.d.ts.map +1 -1
  16. package/dist/callback.js +119 -21
  17. package/dist/callback.js.map +1 -1
  18. package/dist/closure.d.ts +26 -0
  19. package/dist/closure.d.ts.map +1 -0
  20. package/dist/closure.js +99 -0
  21. package/dist/closure.js.map +1 -0
  22. package/dist/descriptors.d.ts +105 -6
  23. package/dist/descriptors.d.ts.map +1 -1
  24. package/dist/descriptors.js +56 -12
  25. package/dist/descriptors.js.map +1 -1
  26. package/dist/error.d.ts +7 -0
  27. package/dist/error.d.ts.map +1 -1
  28. package/dist/error.js +1 -1
  29. package/dist/error.js.map +1 -1
  30. package/dist/fn.d.ts +24 -3
  31. package/dist/fn.d.ts.map +1 -1
  32. package/dist/fn.js +70 -33
  33. package/dist/fn.js.map +1 -1
  34. package/dist/index.d.ts +29 -7
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +26 -4
  37. package/dist/index.js.map +1 -1
  38. package/dist/internal.d.ts +1 -0
  39. package/dist/internal.d.ts.map +1 -1
  40. package/dist/internal.js +1 -0
  41. package/dist/internal.js.map +1 -1
  42. package/dist/library.d.ts +2 -1
  43. package/dist/library.d.ts.map +1 -1
  44. package/dist/library.js +2 -1
  45. package/dist/library.js.map +1 -1
  46. package/dist/lifecycle.d.ts +52 -10
  47. package/dist/lifecycle.d.ts.map +1 -1
  48. package/dist/lifecycle.js +62 -18
  49. package/dist/lifecycle.js.map +1 -1
  50. package/dist/listeners.d.ts +6 -0
  51. package/dist/listeners.d.ts.map +1 -1
  52. package/dist/listeners.js +3 -1
  53. package/dist/listeners.js.map +1 -1
  54. package/dist/mixin.d.ts +16 -2
  55. package/dist/mixin.d.ts.map +1 -1
  56. package/dist/mixin.js +18 -6
  57. package/dist/mixin.js.map +1 -1
  58. package/dist/native-value.d.ts +8 -0
  59. package/dist/native-value.d.ts.map +1 -1
  60. package/dist/native-value.js +12 -3
  61. package/dist/native-value.js.map +1 -1
  62. package/dist/object.d.ts +34 -6
  63. package/dist/object.d.ts.map +1 -1
  64. package/dist/object.js +89 -25
  65. package/dist/object.js.map +1 -1
  66. package/dist/param-spec.d.ts +11 -0
  67. package/dist/param-spec.d.ts.map +1 -0
  68. package/dist/param-spec.js +149 -0
  69. package/dist/param-spec.js.map +1 -0
  70. package/dist/promisify.d.ts +3 -5
  71. package/dist/promisify.d.ts.map +1 -1
  72. package/dist/promisify.js +4 -3
  73. package/dist/promisify.js.map +1 -1
  74. package/dist/properties.d.ts +39 -7
  75. package/dist/properties.d.ts.map +1 -1
  76. package/dist/properties.js +305 -42
  77. package/dist/properties.js.map +1 -1
  78. package/dist/register-class.d.ts +119 -14
  79. package/dist/register-class.d.ts.map +1 -1
  80. package/dist/register-class.js +109 -80
  81. package/dist/register-class.js.map +1 -1
  82. package/dist/registry.d.ts +81 -17
  83. package/dist/registry.d.ts.map +1 -1
  84. package/dist/registry.js +72 -26
  85. package/dist/registry.js.map +1 -1
  86. package/dist/signal.d.ts +22 -3
  87. package/dist/signal.d.ts.map +1 -1
  88. package/dist/signal.js +64 -8
  89. package/dist/signal.js.map +1 -1
  90. package/dist/t.d.ts +44 -0
  91. package/dist/t.d.ts.map +1 -1
  92. package/dist/t.js.map +1 -1
  93. package/dist/type.d.ts +2 -2
  94. package/dist/type.d.ts.map +1 -1
  95. package/dist/type.js +2 -0
  96. package/dist/type.js.map +1 -1
  97. package/dist/value.d.ts +10 -10
  98. package/dist/value.d.ts.map +1 -1
  99. package/dist/value.js +44 -22
  100. package/dist/value.js.map +1 -1
  101. package/dist/vfunc-call.d.ts +37 -0
  102. package/dist/vfunc-call.d.ts.map +1 -0
  103. package/dist/vfunc-call.js +168 -0
  104. package/dist/vfunc-call.js.map +1 -0
  105. package/dist/vfunc-seeds.d.ts +12 -0
  106. package/dist/vfunc-seeds.d.ts.map +1 -0
  107. package/dist/vfunc-seeds.js +19 -0
  108. package/dist/vfunc-seeds.js.map +1 -0
  109. package/dist/vfunc.d.ts +8 -0
  110. package/dist/vfunc.d.ts.map +1 -0
  111. package/dist/vfunc.js +23 -0
  112. package/dist/vfunc.js.map +1 -0
  113. package/package.json +4 -4
  114. package/src/application-class.ts +116 -0
  115. package/src/arg.ts +11 -5
  116. package/src/bind.ts +11 -0
  117. package/src/callback.ts +163 -23
  118. package/src/closure.ts +136 -0
  119. package/src/descriptors.ts +137 -41
  120. package/src/error.ts +8 -1
  121. package/src/fn.ts +104 -39
  122. package/src/index.ts +36 -7
  123. package/src/internal.ts +1 -0
  124. package/src/library.ts +2 -1
  125. package/src/lifecycle.ts +99 -22
  126. package/src/listeners.ts +9 -1
  127. package/src/mixin.ts +27 -8
  128. package/src/native-value.ts +13 -3
  129. package/src/object.ts +128 -34
  130. package/src/param-spec.ts +235 -0
  131. package/src/promisify.ts +8 -8
  132. package/src/properties.ts +459 -45
  133. package/src/register-class.ts +329 -114
  134. package/src/registry.ts +150 -38
  135. package/src/signal.ts +101 -17
  136. package/src/t.ts +44 -0
  137. package/src/type.ts +3 -2
  138. package/src/value.ts +57 -19
  139. package/src/vfunc-call.ts +243 -0
  140. package/src/vfunc-seeds.ts +36 -0
  141. package/src/vfunc.ts +31 -0
package/src/value.ts CHANGED
@@ -9,16 +9,18 @@ import {
9
9
  boxedT,
10
10
  float32T,
11
11
  float64T,
12
+ int8T,
12
13
  int32T,
13
14
  objectT,
14
15
  stringT,
16
+ uint8T,
15
17
  uint32T,
16
18
  uint64T,
17
19
  voidT,
18
20
  } from "./descriptors.js";
19
21
  import { LIB, PARAM_T, VALUE_SIZE, VALUE_T, VARIANT_T } from "./library.js";
20
22
  import { toNative } from "./native-value.js";
21
- import { getHandle, getWrapperClass, wrapHandle } from "./registry.js";
23
+ import { getHandle, getWrapperClass, wrapHandle, wrapObject } from "./registry.js";
22
24
  import {
23
25
  getStrvType,
24
26
  isResolvableDescriptor,
@@ -28,6 +30,7 @@ import {
28
30
  resolveType,
29
31
  TYPE_BOOLEAN,
30
32
  TYPE_BOXED,
33
+ TYPE_CHAR,
31
34
  TYPE_DOUBLE,
32
35
  TYPE_ENUM,
33
36
  TYPE_FLAGS,
@@ -35,13 +38,17 @@ import {
35
38
  TYPE_GTYPE,
36
39
  TYPE_INT,
37
40
  TYPE_INT64,
41
+ TYPE_INTERFACE,
38
42
  TYPE_INVALID,
43
+ TYPE_LONG,
39
44
  TYPE_OBJECT,
40
45
  TYPE_PARAM,
41
46
  TYPE_POINTER,
42
47
  TYPE_STRING,
48
+ TYPE_UCHAR,
43
49
  TYPE_UINT,
44
50
  TYPE_UINT64,
51
+ TYPE_ULONG,
45
52
  TYPE_VARIANT,
46
53
  typeFundamental,
47
54
  typeName,
@@ -53,6 +60,7 @@ type ValueType = {
53
60
  };
54
61
 
55
62
  type ValueGetter = (value: ExternalObject<Handle>) => unknown;
63
+ type ValueWriter = ValueType["set"];
56
64
 
57
65
  const setBoxedCache = createBindCache();
58
66
  const setStaticBoxedCache = createBindCache();
@@ -61,8 +69,12 @@ const gValueInit = bind(LIB, "g_value_init", [VALUE_T, biguint64T], voidT);
61
69
  const gValueCopy = bind(LIB, "g_value_copy", [VALUE_T, VALUE_T], voidT);
62
70
  const booleanValueType = bindValueType("boolean", booleanT);
63
71
  const typeValueType = bindValueType("gtype", biguint64T);
72
+ const scharValueType = bindValueType("schar", int8T);
73
+ const ucharValueType = bindValueType("uchar", uint8T);
64
74
  const intValueType = bindValueType("int", int32T);
65
75
  const uintValueType = bindValueType("uint", uint32T);
76
+ const longValueType = bindValueType("long", bigint64T);
77
+ const ulongValueType = bindValueType("ulong", biguint64T);
66
78
  const int64ValueType = bindValueType("int64", bigint64T);
67
79
  const uint64ValueType = bindValueType("uint64", biguint64T);
68
80
  const floatValueType = bindValueType("float", float32T);
@@ -101,8 +113,12 @@ const PLAIN_VALUE_TYPES: Partial<Record<Descriptor["kind"], ValueType>> = {
101
113
  const PLAIN_VALUE_GETTERS: Map<bigint, ValueGetter> = new Map([
102
114
  [TYPE_BOOLEAN, booleanValueType.get],
103
115
  [TYPE_GTYPE, typeValueType.get],
116
+ [TYPE_CHAR, scharValueType.get],
117
+ [TYPE_UCHAR, ucharValueType.get],
104
118
  [TYPE_INT, intValueType.get],
105
119
  [TYPE_UINT, uintValueType.get],
120
+ [TYPE_LONG, longValueType.get],
121
+ [TYPE_ULONG, ulongValueType.get],
106
122
  [TYPE_INT64, int64ValueType.get],
107
123
  [TYPE_UINT64, uint64ValueType.get],
108
124
  [TYPE_FLOAT, floatValueType.get],
@@ -114,8 +130,12 @@ const PLAIN_VALUE_GETTERS: Map<bigint, ValueGetter> = new Map([
114
130
  const PLAIN_VALUE_SETTERS: Map<bigint, ValueType["set"]> = new Map([
115
131
  [TYPE_BOOLEAN, booleanValueType.set],
116
132
  [TYPE_GTYPE, typeValueType.set],
133
+ [TYPE_CHAR, scharValueType.set],
134
+ [TYPE_UCHAR, ucharValueType.set],
117
135
  [TYPE_INT, intValueType.set],
118
136
  [TYPE_UINT, uintValueType.set],
137
+ [TYPE_LONG, longValueType.set],
138
+ [TYPE_ULONG, ulongValueType.set],
119
139
  [TYPE_INT64, int64ValueType.set],
120
140
  [TYPE_UINT64, uint64ValueType.set],
121
141
  [TYPE_FLOAT, floatValueType.set],
@@ -127,9 +147,11 @@ const PLAIN_VALUE_SETTERS: Map<bigint, ValueType["set"]> = new Map([
127
147
  const WRAPPED_VALUE_SETTERS: Map<bigint, ValueType["set"]> = new Map([
128
148
  [TYPE_STRING, setStringValue],
129
149
  [TYPE_OBJECT, handleSetter(objectValueType)],
150
+ [TYPE_INTERFACE, handleSetter(objectValueType)],
130
151
  [TYPE_PARAM, handleSetter(paramValueType)],
131
152
  [TYPE_VARIANT, handleSetter(variantValueType)],
132
153
  [TYPE_BOXED, setBoxedFromValue],
154
+ [TYPE_POINTER, setPointerValue],
133
155
  ]);
134
156
 
135
157
  const getBoxedTypeName = (type: bigint): string => typeName(type) ?? "GBoxed";
@@ -189,7 +211,7 @@ function copyValue(dest: ExternalObject<Handle>, src: ExternalObject<Handle>): v
189
211
  gValueCopy(src, dest);
190
212
  }
191
213
 
192
- const newTypedValue = (type: bigint): ExternalObject<Handle> => {
214
+ const newValueForType = (type: bigint): ExternalObject<Handle> => {
193
215
  const value = newValue();
194
216
  gValueInit(value, type);
195
217
 
@@ -202,15 +224,15 @@ const newBoxedValue = (
202
224
  resolveSetBind: (name: string) => ValueType["set"],
203
225
  ): ExternalObject<Handle> => {
204
226
  const type = resolveDescriptorType(descriptor);
205
- const value = newTypedValue(type);
227
+ const value = newValueForType(type);
206
228
  resolveSetBind(getBoxedTypeName(type))(value, getHandle(boxed));
207
229
 
208
230
  return value;
209
231
  };
210
232
 
211
233
  /**
212
- * Reads the boxed pointer out of a GValue and returns it wrapped in its registered
213
- * class, or null when the value does not hold a boxed type.
234
+ * Duplicates the boxed value held by a GValue and returns the copy wrapped in the
235
+ * class registered for its GType, or null when the GValue holds no boxed type.
214
236
  */
215
237
  function getBoxedValue(value: ExternalObject<Handle>): object | null {
216
238
  const type = getValueType(value);
@@ -276,8 +298,9 @@ const wrappedValueGetter = (fundamental: bigint): ValueGetter | undefined => {
276
298
  case TYPE_STRING: {
277
299
  return (value) => stringValueType.get(value) ?? null;
278
300
  }
301
+ case TYPE_INTERFACE:
279
302
  case TYPE_OBJECT: {
280
- return (value) => wrapHandle(objectValueType.get(value) as ExternalObject<Handle> | null);
303
+ return (value) => wrapObject(objectValueType.get(value));
281
304
  }
282
305
  case TYPE_PARAM: {
283
306
  return (value) =>
@@ -309,6 +332,14 @@ function setStringValue(value: ExternalObject<Handle>, nativeValue: unknown): vo
309
332
  stringValueType.set(value, nativeValue ?? null);
310
333
  }
311
334
 
335
+ function setPointerValue(value: ExternalObject<Handle>, nativeValue: unknown): void {
336
+ if (nativeValue != null) {
337
+ throw new Error("G_TYPE_POINTER non-null values cannot be marshalled from JS");
338
+ }
339
+
340
+ pointerValueType.set(value, 0);
341
+ }
342
+
312
343
  function handleSetter(target: ValueType): ValueType["set"] {
313
344
  return (value, nativeValue) => {
314
345
  target.set(value, nativeValue == null ? null : getHandle(nativeValue));
@@ -325,22 +356,26 @@ const resolveValueGetter = (fundamental: bigint): ValueGetter | undefined =>
325
356
  const resolveValueSetter = (fundamental: bigint): ValueType["set"] | undefined =>
326
357
  PLAIN_VALUE_SETTERS.get(fundamental) ?? WRAPPED_VALUE_SETTERS.get(fundamental);
327
358
 
328
- /**
329
- * Stores a JavaScript value into an already-initialized GValue, converting it to
330
- * whatever type the GValue holds.
331
- *
332
- * @param value The initialized GValue to write into.
333
- * @param jsValue The JavaScript value to store.
334
- */
335
- function intoValue(value: ExternalObject<Handle>, jsValue: unknown): void {
336
- const type = getValueType(value);
359
+ function setStrvValue(value: ExternalObject<Handle>, jsValue: unknown): void {
360
+ strvValueType.set(value, jsValue ?? null);
361
+ }
362
+
363
+ function valueWriterFor(type: bigint): ValueWriter {
364
+ if (type === getStrvType()) {
365
+ return setStrvValue;
366
+ }
367
+
337
368
  const set = resolveValueSetter(typeFundamental(type));
338
369
 
339
370
  if (set === undefined) {
340
371
  throw new Error(`Unsupported type for intoValue: ${typeName(type) ?? String(type)}`);
341
372
  }
342
373
 
343
- set(value, jsValue);
374
+ return set;
375
+ }
376
+
377
+ function intoValue(value: ExternalObject<Handle>, jsValue: unknown): void {
378
+ valueWriterFor(getValueType(value))(value, jsValue);
344
379
  }
345
380
 
346
381
  const resolveNativeValue = (descriptor: Descriptor, value: unknown): unknown => {
@@ -364,7 +399,7 @@ const resolveValueGType = (descriptor: Descriptor, nativeValue: unknown): bigint
364
399
  function toValue(descriptor: Descriptor, value: unknown): ExternalObject<Handle> {
365
400
  const nativeValue = resolveNativeValue(descriptor, value);
366
401
  const type = resolveValueGType(descriptor, nativeValue);
367
- const gValue = newTypedValue(type);
402
+ const gValue = newValueForType(type);
368
403
  resolveValueType(descriptor).set(gValue, nativeValue);
369
404
 
370
405
  return gValue;
@@ -387,7 +422,7 @@ function fromValue(value: ExternalObject<Handle>): unknown {
387
422
  }
388
423
 
389
424
  function newValueForDescriptor(descriptor: Descriptor): ExternalObject<Handle> {
390
- return newTypedValue(resolveDescriptorType(descriptor));
425
+ return newValueForType(resolveDescriptorType(descriptor));
391
426
  }
392
427
 
393
428
  function outValueForDescriptor(
@@ -401,7 +436,7 @@ function outValueForDescriptor(
401
436
  write(storage, descriptor, 0, initial);
402
437
  }
403
438
 
404
- const value = newTypedValue(TYPE_POINTER);
439
+ const value = newValueForType(TYPE_POINTER);
405
440
  pointerValueType.set(value, storage);
406
441
 
407
442
  return { value, read: () => read(storage, descriptor, 0) };
@@ -424,7 +459,10 @@ export {
424
459
  toValue,
425
460
  fromValue,
426
461
  newValueForDescriptor,
462
+ newValueForType,
427
463
  outValueForDescriptor,
428
464
  outValueForBoxedDescriptor,
429
465
  inoutValueForBoxedDescriptor,
466
+ type ValueWriter,
467
+ valueWriterFor,
430
468
  };
@@ -0,0 +1,243 @@
1
+ import type { BindVfuncOptions } from "@gtkx/native";
2
+ import type { AnyClass } from "@gtkx/utils";
3
+ import { bindVfunc } from "@gtkx/native";
4
+ import { type Arg, isCallerAllocatedArg, requiresInputArg } from "./arg.js";
5
+ import { buildNativeArgTypes, fromNativeCallable } from "./fn.js";
6
+ import { toNative } from "./native-value.js";
7
+ import {
8
+ getClassType,
9
+ getHandle,
10
+ getInterfaceVfuncRegistry,
11
+ getVfuncRegistry,
12
+ instanceClassName,
13
+ resolveWrapperType,
14
+ type VfuncDescriptor,
15
+ } from "./registry.js";
16
+ import { TYPE_INVALID, typeInterfaces, typeIsA, typeParent } from "./type.js";
17
+ import { type RefSeeds, seedsFor } from "./vfunc-seeds.js";
18
+ import { findClassVfuncDescriptor, findInterfaceVfuncDescriptor, vfuncArgs } from "./vfunc.js";
19
+
20
+ type Invoker = (instance: object, inputs: unknown[]) => unknown;
21
+ type InvokerCache = WeakMap<AnyClass, Map<string, Invoker>>;
22
+ type ResolvedSlot = { descriptor: VfuncDescriptor; interfaceType?: bigint };
23
+
24
+ const NO_BASELINE = -1;
25
+
26
+ const SEEDED_SLOTS: Record<string, RefSeeds> = {
27
+ "LayoutManagerClass.measure": new Map([[6, NO_BASELINE], [7, NO_BASELINE]]),
28
+ "WidgetClass.measure": new Map([[5, NO_BASELINE], [6, NO_BASELINE]]),
29
+ };
30
+
31
+ const parentInvokers: InvokerCache = new WeakMap();
32
+ const vfuncInvokers: InvokerCache = new WeakMap();
33
+
34
+ function toNativeInput(arg: Arg, input: unknown): unknown {
35
+ return isCallerAllocatedArg(arg) ? input : toNative(arg.type, input);
36
+ }
37
+
38
+ function bindOptionsFor(
39
+ slot: ResolvedSlot,
40
+ instanceType: bigint | undefined,
41
+ args: Arg[],
42
+ label: string,
43
+ ): BindVfuncOptions {
44
+ const { descriptor, interfaceType } = slot;
45
+
46
+ const options: BindVfuncOptions = {
47
+ byteOffset: descriptor.byteOffset,
48
+ label,
49
+ argDescriptors: buildNativeArgTypes(args, descriptor.canThrow === true),
50
+ returnDescriptor: descriptor.returnDescriptor,
51
+ };
52
+
53
+ if (descriptor.vtableSize !== undefined) {
54
+ options.vtableSize = descriptor.vtableSize;
55
+ }
56
+
57
+ if (instanceType !== undefined) {
58
+ options.instanceType = instanceType;
59
+ }
60
+
61
+ if (interfaceType !== undefined) {
62
+ options.interfaceType = interfaceType;
63
+ }
64
+
65
+ return options;
66
+ }
67
+
68
+ function buildInvoker(slot: ResolvedSlot, instanceType: bigint | undefined, caller: string): Invoker {
69
+ const { descriptor } = slot;
70
+ const args = vfuncArgs(descriptor);
71
+ const canThrow = descriptor.canThrow === true;
72
+ const label = `${descriptor.className}.${descriptor.vfuncName}`;
73
+ const [, ...inputArgs] = args.filter(requiresInputArg);
74
+ let pendingSeeds: RefSeeds | undefined;
75
+
76
+ const takeRefSeeds = (): RefSeeds | undefined => {
77
+ const seeds = pendingSeeds;
78
+ pendingSeeds = undefined;
79
+
80
+ return seeds;
81
+ };
82
+
83
+ const shaped = fromNativeCallable(
84
+ bindVfunc(bindOptionsFor(slot, instanceType, args, label)),
85
+ { args, returns: descriptor.returnDescriptor, canThrow },
86
+ takeRefSeeds,
87
+ );
88
+
89
+ return (instance, inputs) => {
90
+ if (inputs.length !== inputArgs.length) {
91
+ throw new Error(
92
+ `${caller}: ${label} expects ${String(inputArgs.length)} arguments, ` +
93
+ `received ${String(inputs.length)}`,
94
+ );
95
+ }
96
+
97
+ const nativeInputs = inputArgs.map((arg, index) => toNativeInput(arg, inputs[index]));
98
+ pendingSeeds = SEEDED_SLOTS[label] ?? seedsFor(descriptor.argDescriptors, instance);
99
+
100
+ return shaped(getHandle(instance), ...nativeInputs);
101
+ };
102
+ }
103
+
104
+ function cachedInvoker(cache: InvokerCache, owner: AnyClass, key: string, build: () => Invoker): Invoker {
105
+ let byKey = cache.get(owner);
106
+
107
+ if (byKey === undefined) {
108
+ byKey = new Map();
109
+ cache.set(owner, byKey);
110
+ }
111
+
112
+ let invoker = byKey.get(key);
113
+
114
+ if (invoker === undefined) {
115
+ invoker = build();
116
+ byKey.set(key, invoker);
117
+ }
118
+
119
+ return invoker;
120
+ }
121
+
122
+ function resolveParentSlot(klass: AnyClass, parentType: bigint, methodName: string): ResolvedSlot | undefined {
123
+ const classDescriptor = findClassVfuncDescriptor(klass, methodName);
124
+
125
+ if (classDescriptor) {
126
+ return { descriptor: classDescriptor };
127
+ }
128
+
129
+ for (const interfaceType of typeInterfaces(parentType)) {
130
+ const descriptor = findInterfaceVfuncDescriptor(interfaceType, methodName);
131
+
132
+ if (descriptor) {
133
+ return { descriptor, interfaceType };
134
+ }
135
+ }
136
+
137
+ return undefined;
138
+ }
139
+
140
+ function resolveParentType(klass: AnyClass, methodName: string): bigint {
141
+ const gtype = getClassType(klass);
142
+
143
+ if (gtype === TYPE_INVALID) {
144
+ throw new Error(
145
+ `callParent: cannot call '${methodName}' because ${klass.name} was never passed to registerClass`,
146
+ );
147
+ }
148
+
149
+ return typeParent(gtype);
150
+ }
151
+
152
+ function buildParentInvoker(klass: AnyClass, methodName: string): Invoker {
153
+ const parentType = resolveParentType(klass, methodName);
154
+ const slot = resolveParentSlot(klass, parentType, methodName);
155
+
156
+ if (!slot) {
157
+ throw new Error(`callParent: ${klass.name} inherits no '${methodName}' vtable slot`);
158
+ }
159
+
160
+ return buildInvoker(slot, parentType, "callParent");
161
+ }
162
+
163
+ /**
164
+ * Calls the implementation of a virtual function that the parent type of `klass` provides, so an
165
+ * override can chain up to the behavior it replaces. `klass` is the class whose override is
166
+ * running, named lexically rather than taken from `instance`, so each level of a hierarchy reaches
167
+ * exactly one level up. Arguments and the return value follow the same convention the override
168
+ * itself uses: pure out parameters are left out and returned instead, and a slot with several
169
+ * outputs returns them as a tuple. Throws if `klass` was never registered, inherits no such slot,
170
+ * or the parent type leaves the slot empty.
171
+ *
172
+ * @param klass The registered class whose override is chaining up.
173
+ * @param methodName Name of the overridden method, such as `vfuncMeasure`.
174
+ * @param instance The instance the override was invoked on.
175
+ * @param inputs The arguments the override received.
176
+ */
177
+ function callParent(klass: AnyClass, methodName: string, instance: object, ...inputs: unknown[]): unknown {
178
+ const invoker = cachedInvoker(parentInvokers, klass, methodName, () => buildParentInvoker(klass, methodName));
179
+
180
+ return invoker(instance, inputs);
181
+ }
182
+
183
+ function resolveOwnerSlot(owner: AnyClass, key: string): ResolvedSlot {
184
+ const classDescriptor = getVfuncRegistry(owner)?.[key];
185
+
186
+ if (classDescriptor !== undefined) {
187
+ return { descriptor: classDescriptor };
188
+ }
189
+
190
+ const interfaceType = getClassType(owner);
191
+ const descriptor = getInterfaceVfuncRegistry(interfaceType)?.[key];
192
+
193
+ if (descriptor === undefined) {
194
+ throw new Error(`callVfunc: ${owner.name} declares no '${key}' vtable slot`);
195
+ }
196
+
197
+ return { descriptor, interfaceType };
198
+ }
199
+
200
+ function requiresDefaultVtable(slot: ResolvedSlot, instanceType: bigint): boolean {
201
+ return slot.interfaceType !== undefined && !typeIsA(instanceType, slot.interfaceType);
202
+ }
203
+
204
+ function resolveInstanceType(key: string, instance: object, slot: ResolvedSlot): bigint | undefined {
205
+ const instanceType = resolveWrapperType(instance);
206
+
207
+ if (instanceType === TYPE_INVALID) {
208
+ const name = instanceClassName(instance);
209
+ throw new Error(`callVfunc: cannot call '${key}' because ${name} descends from no registered wrapper class`);
210
+ }
211
+
212
+ return requiresDefaultVtable(slot, instanceType) ? undefined : instanceType;
213
+ }
214
+
215
+ /**
216
+ * Calls the implementation of a virtual function a wrapper class or interface declares, backing the
217
+ * `vfunc`-prefixed members the generated bindings emit. The slot is read from the nearest generated
218
+ * wrapper class in the instance's class chain rather than from the instance's own type, so an
219
+ * override reaching it through `super` runs the implementation it replaced instead of re-entering
220
+ * itself. When that wrapper class does not carry the interface the slot belongs to, because a class
221
+ * registered below it adopted the interface, the slot comes from the interface's own default vtable
222
+ * instead, which holds exactly what the adopting class replaced. Arguments and the return value
223
+ * follow the same convention an override uses: pure out parameters are left out and returned
224
+ * instead, and a slot with several outputs returns them as a tuple. Throws if the owner declares no
225
+ * such slot, the instance descends from no wrapper class, or the resolved vtable leaves the slot
226
+ * empty.
227
+ *
228
+ * @param owner The wrapper class or interface declaring the slot.
229
+ * @param key Name of the generated member, such as `vfuncMeasure`.
230
+ * @param instance The instance to invoke the slot on.
231
+ * @param inputs The arguments the slot receives.
232
+ */
233
+ function callVfunc(owner: AnyClass, key: string, instance: object, inputs: unknown[]): unknown {
234
+ const slot = resolveOwnerSlot(owner, key);
235
+ const instanceType = resolveInstanceType(key, instance, slot);
236
+
237
+ const invoker = cachedInvoker(vfuncInvokers, owner, `${key}:${String(instanceType)}`, () =>
238
+ buildInvoker(slot, instanceType, "callVfunc"));
239
+
240
+ return invoker(instance, inputs);
241
+ }
242
+
243
+ export { callParent, callVfunc };
@@ -0,0 +1,36 @@
1
+ import type { Descriptor } from "@gtkx/native";
2
+
3
+ type RefSeeds = Map<number, unknown>;
4
+
5
+ type SeedFrame = {
6
+ argDescriptors: Descriptor[];
7
+ instance: unknown;
8
+ seeds: RefSeeds;
9
+ };
10
+
11
+ const frames: SeedFrame[] = [];
12
+
13
+ const pushSeedFrame = (frame: SeedFrame): void => {
14
+ frames.push(frame);
15
+ };
16
+
17
+ const popSeedFrame = (): void => {
18
+ frames.pop();
19
+ };
20
+
21
+ const isMatchingFrame = (frame: SeedFrame, argDescriptors: Descriptor[], instance: unknown): boolean =>
22
+ frame.argDescriptors === argDescriptors && frame.instance === instance;
23
+
24
+ const seedsFor = (argDescriptors: Descriptor[], instance: unknown): RefSeeds | undefined => {
25
+ for (let index = frames.length - 1; index >= 0; index--) {
26
+ const frame = frames[index];
27
+
28
+ if (frame !== undefined && isMatchingFrame(frame, argDescriptors, instance)) {
29
+ return frame.seeds;
30
+ }
31
+ }
32
+
33
+ return undefined;
34
+ };
35
+
36
+ export { popSeedFrame, pushSeedFrame, type RefSeeds, seedsFor };
package/src/vfunc.ts ADDED
@@ -0,0 +1,31 @@
1
+ import type { Descriptor } from "@gtkx/native";
2
+ import { type AnyClass, getParentClass, walkClassChain } from "@gtkx/utils";
3
+ import type { Arg } from "./arg.js";
4
+ import { isCallerAllocatedOut } from "./callback.js";
5
+ import { getInterfaceVfuncRegistry, getVfuncRegistry, type VfuncDescriptor } from "./registry.js";
6
+
7
+ function findClassVfuncDescriptor(klass: AnyClass, methodName: string): VfuncDescriptor | null {
8
+ return walkClassChain(getParentClass(klass), (cls) => getVfuncRegistry(cls)?.[methodName]) ?? null;
9
+ }
10
+
11
+ function findInterfaceVfuncDescriptor(interfaceGtype: bigint, methodName: string): VfuncDescriptor | undefined {
12
+ return getInterfaceVfuncRegistry(interfaceGtype)?.[methodName];
13
+ }
14
+
15
+ function vfuncArg(descriptor: Descriptor): Arg {
16
+ if (descriptor.kind === "ref") {
17
+ return { type: descriptor.innerDescriptor, direction: descriptor.inout === true ? "inout" : "out" };
18
+ }
19
+
20
+ if (isCallerAllocatedOut(descriptor)) {
21
+ return { type: descriptor, direction: "out", isCallerAllocated: true };
22
+ }
23
+
24
+ return { type: descriptor };
25
+ }
26
+
27
+ function vfuncArgs(descriptor: VfuncDescriptor): Arg[] {
28
+ return descriptor.argDescriptors.map((argDescriptor) => vfuncArg(argDescriptor));
29
+ }
30
+
31
+ export { findClassVfuncDescriptor, findInterfaceVfuncDescriptor, vfuncArgs };