@gtkx/runtime 1.0.0-rc.4 → 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 (133) hide show
  1. package/README.md +5 -5
  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 +2 -1
  7. package/dist/arg.d.ts.map +1 -1
  8. package/dist/arg.js +2 -1
  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 +118 -20
  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 +74 -3
  23. package/dist/descriptors.d.ts.map +1 -1
  24. package/dist/descriptors.js +44 -0
  25. package/dist/descriptors.js.map +1 -1
  26. package/dist/error.d.ts +1 -0
  27. package/dist/error.d.ts.map +1 -1
  28. package/dist/error.js.map +1 -1
  29. package/dist/fn.d.ts +19 -2
  30. package/dist/fn.d.ts.map +1 -1
  31. package/dist/fn.js +64 -27
  32. package/dist/fn.js.map +1 -1
  33. package/dist/index.d.ts +14 -6
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +12 -4
  36. package/dist/index.js.map +1 -1
  37. package/dist/internal.d.ts +1 -0
  38. package/dist/internal.d.ts.map +1 -1
  39. package/dist/internal.js +1 -0
  40. package/dist/internal.js.map +1 -1
  41. package/dist/library.d.ts +2 -1
  42. package/dist/library.d.ts.map +1 -1
  43. package/dist/library.js +2 -1
  44. package/dist/library.js.map +1 -1
  45. package/dist/lifecycle.d.ts +43 -11
  46. package/dist/lifecycle.d.ts.map +1 -1
  47. package/dist/lifecycle.js +62 -19
  48. package/dist/lifecycle.js.map +1 -1
  49. package/dist/listeners.d.ts.map +1 -1
  50. package/dist/listeners.js +3 -1
  51. package/dist/listeners.js.map +1 -1
  52. package/dist/mixin.d.ts +3 -2
  53. package/dist/mixin.d.ts.map +1 -1
  54. package/dist/mixin.js +13 -1
  55. package/dist/mixin.js.map +1 -1
  56. package/dist/native-value.d.ts.map +1 -1
  57. package/dist/native-value.js +4 -3
  58. package/dist/native-value.js.map +1 -1
  59. package/dist/object.d.ts +34 -6
  60. package/dist/object.d.ts.map +1 -1
  61. package/dist/object.js +89 -25
  62. package/dist/object.js.map +1 -1
  63. package/dist/param-spec.d.ts +11 -0
  64. package/dist/param-spec.d.ts.map +1 -0
  65. package/dist/param-spec.js +149 -0
  66. package/dist/param-spec.js.map +1 -0
  67. package/dist/promisify.d.ts.map +1 -1
  68. package/dist/promisify.js +3 -2
  69. package/dist/promisify.js.map +1 -1
  70. package/dist/properties.d.ts +38 -7
  71. package/dist/properties.d.ts.map +1 -1
  72. package/dist/properties.js +305 -42
  73. package/dist/properties.js.map +1 -1
  74. package/dist/register-class.d.ts +117 -12
  75. package/dist/register-class.d.ts.map +1 -1
  76. package/dist/register-class.js +108 -76
  77. package/dist/register-class.js.map +1 -1
  78. package/dist/registry.d.ts +69 -19
  79. package/dist/registry.d.ts.map +1 -1
  80. package/dist/registry.js +70 -26
  81. package/dist/registry.js.map +1 -1
  82. package/dist/signal.d.ts +9 -2
  83. package/dist/signal.d.ts.map +1 -1
  84. package/dist/signal.js +63 -7
  85. package/dist/signal.js.map +1 -1
  86. package/dist/type.d.ts +2 -1
  87. package/dist/type.d.ts.map +1 -1
  88. package/dist/type.js +2 -0
  89. package/dist/type.js.map +1 -1
  90. package/dist/value.d.ts +8 -1
  91. package/dist/value.d.ts.map +1 -1
  92. package/dist/value.js +42 -13
  93. package/dist/value.js.map +1 -1
  94. package/dist/vfunc-call.d.ts +37 -0
  95. package/dist/vfunc-call.d.ts.map +1 -0
  96. package/dist/vfunc-call.js +168 -0
  97. package/dist/vfunc-call.js.map +1 -0
  98. package/dist/vfunc-seeds.d.ts +12 -0
  99. package/dist/vfunc-seeds.d.ts.map +1 -0
  100. package/dist/vfunc-seeds.js +19 -0
  101. package/dist/vfunc-seeds.js.map +1 -0
  102. package/dist/vfunc.d.ts +8 -0
  103. package/dist/vfunc.d.ts.map +1 -0
  104. package/dist/vfunc.js +23 -0
  105. package/dist/vfunc.js.map +1 -0
  106. package/package.json +4 -4
  107. package/src/application-class.ts +116 -0
  108. package/src/arg.ts +2 -1
  109. package/src/bind.ts +11 -0
  110. package/src/callback.ts +162 -22
  111. package/src/closure.ts +136 -0
  112. package/src/descriptors.ts +85 -24
  113. package/src/error.ts +1 -0
  114. package/src/fn.ts +92 -31
  115. package/src/index.ts +21 -6
  116. package/src/internal.ts +1 -0
  117. package/src/library.ts +2 -1
  118. package/src/lifecycle.ts +90 -23
  119. package/src/listeners.ts +3 -1
  120. package/src/mixin.ts +15 -2
  121. package/src/native-value.ts +5 -3
  122. package/src/object.ts +128 -34
  123. package/src/param-spec.ts +235 -0
  124. package/src/promisify.ts +4 -2
  125. package/src/properties.ts +458 -45
  126. package/src/register-class.ts +326 -109
  127. package/src/registry.ts +138 -40
  128. package/src/signal.ts +84 -14
  129. package/src/type.ts +3 -1
  130. package/src/value.ts +55 -10
  131. package/src/vfunc-call.ts +243 -0
  132. package/src/vfunc-seeds.ts +36 -0
  133. package/src/vfunc.ts +31 -0
@@ -0,0 +1,8 @@
1
+ import { type AnyClass } from "@gtkx/utils";
2
+ import type { Arg } from "./arg.js";
3
+ import { type VfuncDescriptor } from "./registry.js";
4
+ declare function findClassVfuncDescriptor(klass: AnyClass, methodName: string): VfuncDescriptor | null;
5
+ declare function findInterfaceVfuncDescriptor(interfaceGtype: bigint, methodName: string): VfuncDescriptor | undefined;
6
+ declare function vfuncArgs(descriptor: VfuncDescriptor): Arg[];
7
+ export { findClassVfuncDescriptor, findInterfaceVfuncDescriptor, vfuncArgs };
8
+ //# sourceMappingURL=vfunc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vfunc.d.ts","sourceRoot":"","sources":["../src/vfunc.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,QAAQ,EAAkC,MAAM,aAAa,CAAC;AAC5E,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAEpC,OAAO,EAA+C,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAElG,iBAAS,wBAAwB,CAAC,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAE7F;AAED,iBAAS,4BAA4B,CAAC,cAAc,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAE7G;AAcD,iBAAS,SAAS,CAAC,UAAU,EAAE,eAAe,GAAG,GAAG,EAAE,CAErD;AAED,OAAO,EAAE,wBAAwB,EAAE,4BAA4B,EAAE,SAAS,EAAE,CAAC"}
package/dist/vfunc.js ADDED
@@ -0,0 +1,23 @@
1
+ import { getParentClass, walkClassChain } from "@gtkx/utils";
2
+ import { isCallerAllocatedOut } from "./callback.js";
3
+ import { getInterfaceVfuncRegistry, getVfuncRegistry } from "./registry.js";
4
+ function findClassVfuncDescriptor(klass, methodName) {
5
+ return walkClassChain(getParentClass(klass), (cls) => getVfuncRegistry(cls)?.[methodName]) ?? null;
6
+ }
7
+ function findInterfaceVfuncDescriptor(interfaceGtype, methodName) {
8
+ return getInterfaceVfuncRegistry(interfaceGtype)?.[methodName];
9
+ }
10
+ function vfuncArg(descriptor) {
11
+ if (descriptor.kind === "ref") {
12
+ return { type: descriptor.innerDescriptor, direction: descriptor.inout === true ? "inout" : "out" };
13
+ }
14
+ if (isCallerAllocatedOut(descriptor)) {
15
+ return { type: descriptor, direction: "out", isCallerAllocated: true };
16
+ }
17
+ return { type: descriptor };
18
+ }
19
+ function vfuncArgs(descriptor) {
20
+ return descriptor.argDescriptors.map((argDescriptor) => vfuncArg(argDescriptor));
21
+ }
22
+ export { findClassVfuncDescriptor, findInterfaceVfuncDescriptor, vfuncArgs };
23
+ //# sourceMappingURL=vfunc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vfunc.js","sourceRoot":"","sources":["../src/vfunc.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE5E,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,yBAAyB,EAAE,gBAAgB,EAAwB,MAAM,eAAe,CAAC;AAElG,SAAS,wBAAwB,CAAC,KAAe,EAAE,UAAkB;IACjE,OAAO,cAAc,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,IAAI,IAAI,CAAC;AACvG,CAAC;AAED,SAAS,4BAA4B,CAAC,cAAsB,EAAE,UAAkB;IAC5E,OAAO,yBAAyB,CAAC,cAAc,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC;AACnE,CAAC;AAED,SAAS,QAAQ,CAAC,UAAsB;IACpC,IAAI,UAAU,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;QAC5B,OAAO,EAAE,IAAI,EAAE,UAAU,CAAC,eAAe,EAAE,SAAS,EAAE,UAAU,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC;IACxG,CAAC;IAED,IAAI,oBAAoB,CAAC,UAAU,CAAC,EAAE,CAAC;QACnC,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,KAAK,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAAC;IAC3E,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;AAChC,CAAC;AAED,SAAS,SAAS,CAAC,UAA2B;IAC1C,OAAO,UAAU,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,aAAa,EAAE,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC;AACrF,CAAC;AAED,OAAO,EAAE,wBAAwB,EAAE,4BAA4B,EAAE,SAAS,EAAE,CAAC","sourcesContent":["import type { Descriptor } from \"@gtkx/native\";\nimport { type AnyClass, getParentClass, walkClassChain } from \"@gtkx/utils\";\nimport type { Arg } from \"./arg.js\";\nimport { isCallerAllocatedOut } from \"./callback.js\";\nimport { getInterfaceVfuncRegistry, getVfuncRegistry, type VfuncDescriptor } from \"./registry.js\";\n\nfunction findClassVfuncDescriptor(klass: AnyClass, methodName: string): VfuncDescriptor | null {\n return walkClassChain(getParentClass(klass), (cls) => getVfuncRegistry(cls)?.[methodName]) ?? null;\n}\n\nfunction findInterfaceVfuncDescriptor(interfaceGtype: bigint, methodName: string): VfuncDescriptor | undefined {\n return getInterfaceVfuncRegistry(interfaceGtype)?.[methodName];\n}\n\nfunction vfuncArg(descriptor: Descriptor): Arg {\n if (descriptor.kind === \"ref\") {\n return { type: descriptor.innerDescriptor, direction: descriptor.inout === true ? \"inout\" : \"out\" };\n }\n\n if (isCallerAllocatedOut(descriptor)) {\n return { type: descriptor, direction: \"out\", isCallerAllocated: true };\n }\n\n return { type: descriptor };\n}\n\nfunction vfuncArgs(descriptor: VfuncDescriptor): Arg[] {\n return descriptor.argDescriptors.map((argDescriptor) => vfuncArg(argDescriptor));\n}\n\nexport { findClassVfuncDescriptor, findInterfaceVfuncDescriptor, vfuncArgs };\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gtkx/runtime",
3
- "version": "1.0.0-rc.4",
3
+ "version": "1.0.0",
4
4
  "description": "FFI runtime the generated GTKX bindings call into: type descriptors, GValue marshalling, signals, GObject lifecycle",
5
5
  "keywords": [
6
6
  "gtkx",
@@ -45,11 +45,11 @@
45
45
  "node": ">=24"
46
46
  },
47
47
  "dependencies": {
48
- "@gtkx/native": "1.0.0-rc.4",
49
- "@gtkx/utils": "1.0.0-rc.4"
48
+ "@gtkx/native": "1.0.0",
49
+ "@gtkx/utils": "1.0.0"
50
50
  },
51
51
  "devDependencies": {
52
- "@gtkx/vitest": "1.0.0-rc.4"
52
+ "@gtkx/vitest": "1.0.0"
53
53
  },
54
54
  "scripts": {
55
55
  "release": "tsx ../../scripts/release-package.ts"
@@ -0,0 +1,116 @@
1
+ import { type AnyClass, getOrInsert } from "@gtkx/utils";
2
+ import { registerClass } from "./register-class.js";
3
+ import { getClassType } from "./registry.js";
4
+ import { typeName } from "./type.js";
5
+ import { callParent } from "./vfunc-call.js";
6
+
7
+ type CommandLineResult = [boolean, string[], number];
8
+
9
+ /**
10
+ * The GIO application surface {@link createApplication} builds on, which `Gio.Application` and
11
+ * every subclass of it satisfies structurally.
12
+ */
13
+ type CommandLineApplication = {
14
+ /** Marks the application as quitting, so GLib's own `run` returns without driving a main loop. */
15
+ quit(): void;
16
+ };
17
+
18
+ type LocalCommandLineApplication = {
19
+ runLocalCommandLine(argv: string[]): CommandLineResult;
20
+ };
21
+
22
+ type ShutdownApplication = {
23
+ run(argv: string[]): number;
24
+ };
25
+
26
+ /** An application class together with the construct properties it accepts. */
27
+ type ApplicationClass<T extends CommandLineApplication, P> = AnyClass<T> & (new (props: P) => T);
28
+
29
+ const derivedClasses: WeakMap<AnyClass, AnyClass> = new WeakMap();
30
+ const derivedApplicationClasses: Set<AnyClass> = new Set();
31
+ const quitApplications: WeakSet<object> = new WeakSet();
32
+ const shuttingDownApplications: WeakSet<object> = new WeakSet();
33
+
34
+ const derivedTypeName = (base: AnyClass): string => `Gtkx${typeName(getClassType(base)) ?? base.name}`;
35
+
36
+ const buildApplicationClass = (base: AnyClass<CommandLineApplication>): AnyClass<CommandLineApplication> => {
37
+ class DerivedApplication extends base {
38
+ runLocalCommandLine(argv: string[]): CommandLineResult {
39
+ return this.vfuncLocalCommandLine(argv);
40
+ }
41
+
42
+ protected vfuncLocalCommandLine(argv: string[]): CommandLineResult {
43
+ if (shuttingDownApplications.has(this)) {
44
+ this.quit();
45
+
46
+ return [true, argv, 0];
47
+ }
48
+
49
+ return callParent(DerivedApplication, "vfuncLocalCommandLine", this, argv) as CommandLineResult;
50
+ }
51
+
52
+ protected vfuncNameLost(): boolean {
53
+ quitApplications.add(this);
54
+
55
+ return callParent(DerivedApplication, "vfuncNameLost", this) as boolean;
56
+ }
57
+
58
+ override quit(): void {
59
+ quitApplications.add(this);
60
+ super.quit();
61
+ }
62
+ }
63
+
64
+ const derived = registerClass(DerivedApplication, { typeName: derivedTypeName(base) });
65
+ derivedApplicationClasses.add(derived);
66
+
67
+ return derived;
68
+ };
69
+
70
+ const isDerivedApplication = (application: object): application is LocalCommandLineApplication => {
71
+ for (const derived of derivedApplicationClasses) {
72
+ if (application instanceof derived) {
73
+ return true;
74
+ }
75
+ }
76
+
77
+ return false;
78
+ };
79
+
80
+ const shutDownThroughRun = (application: ShutdownApplication): void => {
81
+ if (!isDerivedApplication(application) || quitApplications.has(application)) {
82
+ return;
83
+ }
84
+
85
+ shuttingDownApplications.add(application);
86
+
87
+ try {
88
+ application.run([]);
89
+ } finally {
90
+ shuttingDownApplications.delete(application);
91
+ }
92
+ };
93
+
94
+ const deriveApplicationClass = <T extends CommandLineApplication>(base: AnyClass<T>): AnyClass<T> =>
95
+ getOrInsert(derivedClasses, base, () => buildApplicationClass(base)) as AnyClass<T>;
96
+
97
+ /**
98
+ * Constructs an application GTKX can shut down. GLib parses an application's command line at most
99
+ * once per instance and crashes on a second parse, so {@link runApplication} and
100
+ * {@link quitApplication} only accept an application built here.
101
+ *
102
+ * @param base The application class to construct, such as `Gtk.Application`.
103
+ * @param props Construct properties, passed through unchanged.
104
+ * @returns An instance of a class derived from `base`, registered once per base class as its own GType.
105
+ */
106
+ const createApplication = <T extends CommandLineApplication, P>(base: ApplicationClass<T, P>, props: P): T =>
107
+ new (deriveApplicationClass(base) as new (props: P) => T)(props);
108
+
109
+ export {
110
+ createApplication,
111
+ isDerivedApplication,
112
+ shutDownThroughRun,
113
+ type ApplicationClass,
114
+ type CommandLineApplication,
115
+ type LocalCommandLineApplication,
116
+ };
package/src/arg.ts CHANGED
@@ -16,5 +16,6 @@ const isOutputArg = (arg: Arg): boolean => arg.direction !== undefined;
16
16
  const isInoutArg = (arg: Arg): boolean => arg.direction === "inout";
17
17
  const isCallerAllocatedArg = (arg: Arg): boolean => arg.isCallerAllocated === true;
18
18
  const isRefArg = (arg: Arg): boolean => arg.direction !== undefined && arg.isCallerAllocated !== true;
19
+ const requiresInputArg = (arg: Arg): boolean => !isRefArg(arg) || isInoutArg(arg);
19
20
 
20
- export { isOutputArg, isInoutArg, isCallerAllocatedArg, isRefArg, type Arg };
21
+ export { isOutputArg, isInoutArg, isCallerAllocatedArg, isRefArg, requiresInputArg, type Arg };
package/src/bind.ts CHANGED
@@ -1,5 +1,16 @@
1
1
  import { call, type Descriptor, bind as nativeBind } from "@gtkx/native";
2
2
 
3
+ /**
4
+ * Precompiles a call to a C function, marshalling the values it is given through the argument
5
+ * descriptors and the result through the return one. The symbol is resolved in the shared library
6
+ * on the first call.
7
+ *
8
+ * @param sharedLibrary The shared library to resolve the symbol in.
9
+ * @param symbol The name of the C function to bind.
10
+ * @param argDescriptors Describe the C arguments, in declaration order.
11
+ * @param returnDescriptor Describes the C return value.
12
+ * @returns A function calling the bound symbol.
13
+ */
3
14
  function bind(
4
15
  sharedLibrary: string,
5
16
  symbol: string,
package/src/callback.ts CHANGED
@@ -4,10 +4,13 @@ import { fromNative, toNative } from "./native-value.js";
4
4
  import { getHandle } from "./registry.js";
5
5
  import { splitTupleResult } from "./tuple.js";
6
6
  import { copyValue } from "./value.js";
7
+ import { popSeedFrame, pushSeedFrame, type RefSeeds } from "./vfunc-seeds.js";
7
8
 
8
9
  type Callback = (...args: unknown[]) => unknown;
9
10
  type CallbackReceiver = "this" | "emitter" | "none";
10
- type OutParam = { value: unknown; descriptor: Descriptor };
11
+ type OutParam = { value: unknown; descriptor: Descriptor; argIndex: number };
12
+ type LengthLink = { target: OutParam; sourceIndex: number };
13
+ type OutParamGroups = { lengthLinks: LengthLink[]; valueParams: OutParam[] };
11
14
 
12
15
  type CallbackSpec = {
13
16
  argDescriptors: Descriptor[];
@@ -21,6 +24,8 @@ type CallbackPlan = {
21
24
  returnDescriptor: Descriptor;
22
25
  start: number;
23
26
  receiver: CallbackReceiver;
27
+ hasOutParams: boolean;
28
+ hasRefOutParams: boolean;
24
29
  };
25
30
 
26
31
  const fillCallerAllocatedBuffer = (descriptor: Descriptor, target: object, source: object): void => {
@@ -46,23 +51,23 @@ const collectRefArg = (
46
51
  descriptor: RefDescriptor,
47
52
  wrappedValue: unknown,
48
53
  inputs: unknown[],
49
- outParams: OutParam[],
54
+ out: { params: OutParam[]; argIndex: number },
50
55
  ): void => {
51
56
  if (descriptor.inout === true) {
52
57
  inputs.push((wrappedValue as { value: unknown }).value);
53
58
  }
54
59
 
55
- outParams.push({ value: wrappedValue, descriptor });
60
+ out.params.push({ value: wrappedValue, descriptor, argIndex: out.argIndex });
56
61
  };
57
62
 
58
63
  const collectCallbackArg = (
59
64
  descriptor: Descriptor | undefined,
60
65
  wrappedValue: unknown,
61
66
  inputs: unknown[],
62
- outParams: OutParam[],
67
+ out: { params: OutParam[]; argIndex: number },
63
68
  ): void => {
64
69
  if (descriptor?.kind === "ref") {
65
- collectRefArg(descriptor, wrappedValue, inputs, outParams);
70
+ collectRefArg(descriptor, wrappedValue, inputs, out);
66
71
 
67
72
  return;
68
73
  }
@@ -70,7 +75,7 @@ const collectCallbackArg = (
70
75
  inputs.push(wrappedValue);
71
76
 
72
77
  if (descriptor !== undefined && isCallerAllocatedOut(descriptor)) {
73
- outParams.push({ value: wrappedValue, descriptor });
78
+ out.params.push({ value: wrappedValue, descriptor, argIndex: out.argIndex });
74
79
  }
75
80
  };
76
81
 
@@ -80,43 +85,173 @@ const partitionCallbackArgs = (
80
85
  start: number,
81
86
  ): { inputs: unknown[]; outParams: OutParam[] } => {
82
87
  const inputs: unknown[] = [];
83
- const outParams: OutParam[] = [];
88
+ const params: OutParam[] = [];
89
+ const collected = { params, argIndex: start };
84
90
 
85
91
  for (let i = start; i < effectiveTypes.length; i++) {
86
- collectCallbackArg(effectiveTypes[i], wrapped[i], inputs, outParams);
92
+ collected.argIndex = i;
93
+ collectCallbackArg(effectiveTypes[i], wrapped[i], inputs, collected);
87
94
  }
88
95
 
89
- return { inputs, outParams };
96
+ return { inputs, outParams: params };
90
97
  };
91
98
 
92
- const writeOutParams = (outParams: OutParam[], outValues: unknown[]): void => {
99
+ const hasOutParamArg = (descriptor: Descriptor | undefined): boolean =>
100
+ descriptor !== undefined && (descriptor.kind === "ref" || isCallerAllocatedOut(descriptor));
101
+
102
+ const hasRefOutParamArg = (descriptor: Descriptor | undefined): boolean =>
103
+ descriptor?.kind === "ref" && descriptor.inout !== true;
104
+
105
+ const haveOutParamArgs = (effectiveTypes: Descriptor[], start: number): boolean =>
106
+ effectiveTypes.slice(start).some((descriptor) => hasOutParamArg(descriptor));
107
+
108
+ const haveRefOutParamArgs = (effectiveTypes: Descriptor[], start: number): boolean =>
109
+ effectiveTypes.slice(start).some((descriptor) => hasRefOutParamArg(descriptor));
110
+
111
+ const sizeParamIndexFor = (descriptor: Descriptor): number | undefined => {
112
+ if (descriptor.kind !== "ref") {
113
+ return undefined;
114
+ }
115
+
116
+ const { innerDescriptor } = descriptor;
117
+
118
+ return innerDescriptor.kind === "array" ? innerDescriptor.sizeParamIndex : undefined;
119
+ };
120
+
121
+ const lengthOutParamIndices = (outParams: OutParam[]): Map<number, number> => {
122
+ const indices: Map<number, number> = new Map();
123
+
124
+ for (const outParam of outParams) {
125
+ const sizeParamIndex = sizeParamIndexFor(outParam.descriptor);
126
+
127
+ if (sizeParamIndex !== undefined) {
128
+ indices.set(sizeParamIndex, outParam.argIndex);
129
+ }
130
+ }
131
+
132
+ return indices;
133
+ };
134
+
135
+ const writeOutParams = (outParams: OutParam[], outValues: unknown[]): Map<number, unknown> => {
136
+ const written: Map<number, unknown> = new Map();
137
+
93
138
  for (const [position, outParam] of outParams.entries()) {
94
139
  const outValue = outValues[position];
140
+ const { descriptor } = outParam;
141
+ written.set(outParam.argIndex, outValue);
95
142
 
96
- if (outParam.descriptor.kind === "ref") {
97
- (outParam.value as { value: unknown }).value = outValue;
143
+ if (descriptor.kind === "ref") {
144
+ (outParam.value as { value: unknown }).value = toNative(descriptor.innerDescriptor, outValue);
98
145
  } else if (outValue != null && outParam.value != null) {
99
- fillCallerAllocatedBuffer(outParam.descriptor, outParam.value, outValue);
146
+ fillCallerAllocatedBuffer(descriptor, outParam.value, outValue);
100
147
  }
101
148
  }
149
+
150
+ return written;
151
+ };
152
+
153
+ const groupOutParams = (outParams: OutParam[]): OutParamGroups => {
154
+ const lengths = lengthOutParamIndices(outParams);
155
+ const groups: OutParamGroups = { lengthLinks: [], valueParams: [] };
156
+
157
+ for (const outParam of outParams) {
158
+ const sourceIndex = lengths.get(outParam.argIndex);
159
+
160
+ if (sourceIndex === undefined) {
161
+ groups.valueParams.push(outParam);
162
+ } else {
163
+ groups.lengthLinks.push({ target: outParam, sourceIndex });
164
+ }
165
+ }
166
+
167
+ return groups;
168
+ };
169
+
170
+ const getFoldedLength = (source: unknown): number => {
171
+ const length = (source as { length?: unknown } | null | undefined)?.length;
172
+
173
+ return typeof length === "number" ? length : 0;
174
+ };
175
+
176
+ const writeFoldedLengths = (lengthLinks: LengthLink[], written: Map<number, unknown>): void => {
177
+ for (const link of lengthLinks) {
178
+ (link.target.value as { value: unknown }).value = getFoldedLength(written.get(link.sourceIndex));
179
+ }
180
+ };
181
+
182
+ const collectRefSeeds = (outParams: OutParam[]): RefSeeds => {
183
+ const seeds: RefSeeds = new Map();
184
+
185
+ for (const outParam of outParams) {
186
+ const seed = (outParam.value as { value?: unknown } | null)?.value;
187
+
188
+ if (seed != null && hasRefOutParamArg(outParam.descriptor)) {
189
+ seeds.set(outParam.argIndex, seed);
190
+ }
191
+ }
192
+
193
+ return seeds;
194
+ };
195
+
196
+ const applyCallback = (plan: CallbackPlan, thisArg: unknown, inputs: unknown[], outParams: OutParam[]): unknown => {
197
+ if (!plan.hasRefOutParams) {
198
+ return plan.fn.apply(thisArg, inputs);
199
+ }
200
+
201
+ pushSeedFrame({ argDescriptors: plan.effectiveTypes, instance: thisArg, seeds: collectRefSeeds(outParams) });
202
+
203
+ try {
204
+ return plan.fn.apply(thisArg, inputs);
205
+ } finally {
206
+ popSeedFrame();
207
+ }
102
208
  };
103
209
 
104
210
  const getThisArg = (receiver: CallbackReceiver, wrapped: unknown[]): unknown =>
105
211
  receiver === "this" ? (wrapped[0] ?? null) : null;
106
212
 
213
+ const wrapCallbackArgs = (effectiveTypes: Descriptor[], rawArgs: unknown[]): void => {
214
+ let index = 0;
215
+
216
+ for (const descriptor of effectiveTypes) {
217
+ rawArgs[index] = fromNative(descriptor, rawArgs[index]);
218
+ index += 1;
219
+ }
220
+ };
221
+
222
+ const trimCallbackInputs = (plan: CallbackPlan, wrapped: unknown[]): unknown[] => {
223
+ const count = plan.effectiveTypes.length;
224
+
225
+ if (wrapped.length > count) {
226
+ wrapped.length = count;
227
+ }
228
+
229
+ for (let index = 0; index < plan.start; index++) {
230
+ wrapped.shift();
231
+ }
232
+
233
+ return wrapped;
234
+ };
235
+
107
236
  const runCallback = (plan: CallbackPlan, rawArgs: unknown[]): unknown => {
108
237
  const { effectiveTypes, returnDescriptor } = plan;
109
- const wrapped = effectiveTypes.map((descriptor, i) => fromNative(descriptor, rawArgs[i]));
110
- const thisArg = getThisArg(plan.receiver, wrapped);
111
- const { inputs, outParams } = partitionCallbackArgs(effectiveTypes, wrapped, plan.start);
112
- const result = plan.fn.apply(thisArg, inputs);
238
+ wrapCallbackArgs(effectiveTypes, rawArgs);
239
+ const thisArg = getThisArg(plan.receiver, rawArgs);
240
+
241
+ if (!plan.hasOutParams) {
242
+ return toNative(returnDescriptor, plan.fn.apply(thisArg, trimCallbackInputs(plan, rawArgs)));
243
+ }
244
+
245
+ const { inputs, outParams } = partitionCallbackArgs(effectiveTypes, rawArgs, plan.start);
246
+ const result = applyCallback(plan, thisArg, inputs, outParams);
113
247
 
114
248
  if (outParams.length === 0) {
115
249
  return toNative(returnDescriptor, result);
116
250
  }
117
251
 
118
- const { primary, outValues } = splitTupleResult(result, returnDescriptor.kind !== "void", outParams.length);
119
- writeOutParams(outParams, outValues);
252
+ const { lengthLinks, valueParams } = groupOutParams(outParams);
253
+ const { primary, outValues } = splitTupleResult(result, returnDescriptor.kind !== "void", valueParams.length);
254
+ writeFoldedLengths(lengthLinks, writeOutParams(valueParams, outValues));
120
255
 
121
256
  return toNative(returnDescriptor, primary);
122
257
  };
@@ -135,15 +270,20 @@ const wrapCallbackValue = (spec: CallbackDescriptor, callback: unknown): unknown
135
270
  callback == null ? callback : wrapCallback(callback as Callback, spec, "none");
136
271
 
137
272
  function wrapCallback(fn: Callback, spec: CallbackSpec, receiver: CallbackReceiver): Callback {
273
+ const effectiveTypes = getEffectiveTypes(spec);
274
+ const start = receiver === "none" ? 0 : 1;
275
+
138
276
  const plan: CallbackPlan = {
139
277
  fn,
140
- effectiveTypes: getEffectiveTypes(spec),
278
+ effectiveTypes,
141
279
  returnDescriptor: spec.returnDescriptor,
142
- start: receiver === "none" ? 0 : 1,
280
+ start,
143
281
  receiver,
282
+ hasOutParams: haveOutParamArgs(effectiveTypes, start),
283
+ hasRefOutParams: haveRefOutParamArgs(effectiveTypes, start),
144
284
  };
145
285
 
146
286
  return (...rawArgs: unknown[]): unknown => runCallback(plan, rawArgs);
147
287
  }
148
288
 
149
- export { wrapCallbackValue, wrapCallback };
289
+ export { isCallerAllocatedOut, wrapCallbackValue, wrapCallback };
package/src/closure.ts ADDED
@@ -0,0 +1,136 @@
1
+ import { type ExternalObject, type Handle, read } from "@gtkx/native";
2
+ import { type AnyClass } from "@gtkx/utils";
3
+ import { bind } from "./bind.js";
4
+ import { boxedT, callbackT, sizedArrayT, structT, uint32T, uint64T, voidT } from "./descriptors.js";
5
+ import { CLOSURE_SIZE, LIB, VALUE_SIZE, VALUE_T } from "./library.js";
6
+ import { getClassType, getHandle, getWrapperClass, instanceClassName, wrapHandle } from "./registry.js";
7
+ import { resolveBoxedType, typeIsA } from "./type.js";
8
+ import { fromValue, getValueType, intoValue } from "./value.js";
9
+
10
+ /**
11
+ * Handler backing a `GClosure`. It receives the closure's parameters already converted from their
12
+ * `GValue`s, and its result is written back into the closure's return `GValue` when it has one.
13
+ *
14
+ * A parameter that is itself a `GValue`, such as the target a property binding transform fills in,
15
+ * arrives as the caller's own `GObject.Value` rather than a copy, so writing to it is how the
16
+ * handler produces that parameter's value. It stops being valid once the handler returns.
17
+ */
18
+ type ClosureCallback = (...args: never[]) => unknown;
19
+
20
+ const CCLOSURE_CALLBACK_OFFSET = CLOSURE_SIZE;
21
+ const N_PARAM_VALUES_INDEX = 2;
22
+ const MARSHAL_DATA_INDEX = 5;
23
+ const CLOSURE_T = boxedT("GClosure", { sharedLibrary: LIB, getTypeFnName: "g_closure_get_type" });
24
+
25
+ const OWNED_CLOSURE_T = boxedT("GClosure", {
26
+ ownership: "full",
27
+ sharedLibrary: LIB,
28
+ getTypeFnName: "g_closure_get_type",
29
+ });
30
+
31
+ const MARSHAL_VALUE_T = boxedT("GValue", {
32
+ sharedLibrary: LIB,
33
+ getTypeFnName: "g_value_get_type",
34
+ size: VALUE_SIZE,
35
+ isCallerAllocated: true,
36
+ });
37
+
38
+ const MARSHAL_T = callbackT(
39
+ [
40
+ CLOSURE_T,
41
+ MARSHAL_VALUE_T,
42
+ uint32T,
43
+ sizedArrayT(MARSHAL_VALUE_T, N_PARAM_VALUES_INDEX, "borrowed", VALUE_SIZE),
44
+ uint64T,
45
+ uint64T,
46
+ ],
47
+ voidT,
48
+ {
49
+ hasUserData: true,
50
+ userDataIndex: MARSHAL_DATA_INDEX,
51
+ hasDestroy: true,
52
+ destroyKind: "closureNotify",
53
+ scope: "notified",
54
+ },
55
+ );
56
+
57
+ const NESTED_VALUE_T = structT("borrowed");
58
+ const gCclosureNew = bind(LIB, "g_cclosure_new", [MARSHAL_T], OWNED_CLOSURE_T);
59
+ const gValueGetBoxed = bind(LIB, "g_value_get_boxed", [VALUE_T], NESTED_VALUE_T);
60
+ const gClosureRef = bind(LIB, "g_closure_ref", [CLOSURE_T], uint64T);
61
+ const gClosureSink = bind(LIB, "g_closure_sink", [CLOSURE_T], voidT);
62
+ const gClosureSetMarshal = bind(LIB, "g_closure_set_marshal", [CLOSURE_T, uint64T], voidT);
63
+
64
+ const isClosureInstance = (value: object): boolean =>
65
+ typeIsA(getClassType(value.constructor as AnyClass), resolveBoxedType(CLOSURE_T));
66
+
67
+ const getNestedValue = (param: ExternalObject<Handle>): object | null =>
68
+ wrapHandle(gValueGetBoxed(param) as ExternalObject<Handle> | null, getWrapperClass(resolveBoxedType(VALUE_T)));
69
+
70
+ const fromParamValue = (param: ExternalObject<Handle>): unknown =>
71
+ getValueType(param) === resolveBoxedType(VALUE_T) ? getNestedValue(param) : fromValue(param);
72
+
73
+ function describeValue(value: unknown): string {
74
+ if (value === null) {
75
+ return "null";
76
+ }
77
+
78
+ if (typeof value !== "object") {
79
+ return typeof value;
80
+ }
81
+
82
+ return instanceClassName(value);
83
+ }
84
+
85
+ function marshalFor(callback: ClosureCallback): (...args: unknown[]) => void {
86
+ return (_closure: unknown, returnValue: unknown, _count: unknown, paramValues: unknown): void => {
87
+ const values = paramValues as ExternalObject<Handle>[];
88
+ const args = values.map((value) => fromParamValue(value));
89
+ const result = (callback as (...values: unknown[]) => unknown)(...(args as never[]));
90
+
91
+ if (returnValue !== null) {
92
+ intoValue(returnValue as ExternalObject<Handle>, result);
93
+ }
94
+ };
95
+ }
96
+
97
+ function newClosure(callback: ClosureCallback): ExternalObject<Handle> {
98
+ const handle = gCclosureNew(marshalFor(callback)) as ExternalObject<Handle>;
99
+ const marshal = read(handle, uint64T, CCLOSURE_CALLBACK_OFFSET);
100
+ gClosureRef(handle);
101
+ gClosureSink(handle);
102
+ gClosureSetMarshal(handle, marshal);
103
+
104
+ return handle;
105
+ }
106
+
107
+ /**
108
+ * Marshals a value passed where a `GObject.Closure` is expected into a closure handle: a function
109
+ * becomes a new `GClosure` dispatching into it, and an existing closure yields its own handle.
110
+ * @param value Function to wrap, or an already-built closure.
111
+ * @throws {ClosureMarshalError} When the value is neither.
112
+ */
113
+ function toClosure(value: unknown): ExternalObject<Handle> {
114
+ if (typeof value === "function") {
115
+ return newClosure(value as ClosureCallback);
116
+ }
117
+
118
+ if (typeof value === "object" && value !== null && isClosureInstance(value)) {
119
+ return getHandle(value);
120
+ }
121
+
122
+ throw new ClosureMarshalError(`Cannot marshal ${describeValue(value)} into a GObject.Closure`);
123
+ }
124
+
125
+ /** Same as {@link toClosure}, but passes a null or undefined value through as no closure at all. */
126
+ function tryToClosure(value: unknown): ExternalObject<Handle> | undefined {
127
+ return value == null ? undefined : toClosure(value);
128
+ }
129
+
130
+ /** Thrown when a value passed where a `GObject.Closure` is expected cannot be marshalled into one. */
131
+ class ClosureMarshalError extends TypeError {
132
+ /** Name callers match on when the error is caught as a plain `TypeError`. */
133
+ public override name = "ClosureMarshalError";
134
+ }
135
+
136
+ export { type ClosureCallback, ClosureMarshalError, toClosure, tryToClosure };