@thi.ng/wasm-api 2.3.31 → 2.4.1
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.
- package/CHANGELOG.md +17 -1
- package/README.md +7 -9
- package/package.json +11 -11
- package/zig/build-v0.14.zig +131 -0
- package/zig/build.zig +13 -11
- package/zig/lib.zig +1 -1
- package/zig/managed-index.zig +38 -35
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
-
- **Last updated**: 2025-
|
|
3
|
+
- **Last updated**: 2025-09-02T13:15:31Z
|
|
4
4
|
- **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
|
|
5
5
|
|
|
6
6
|
All notable changes to this project will be documented in this file.
|
|
@@ -11,6 +11,22 @@ See [Conventional Commits](https://conventionalcommits.org/) for commit guidelin
|
|
|
11
11
|
**Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes
|
|
12
12
|
and/or version bumps of transitive dependencies.
|
|
13
13
|
|
|
14
|
+
### [2.4.1](https://github.com/thi-ng/umbrella/tree/@thi.ng/wasm-api@2.4.1) (2025-09-02)
|
|
15
|
+
|
|
16
|
+
#### ♻️ Refactoring
|
|
17
|
+
|
|
18
|
+
- update zig `_wasm_allocate()` & `ManagedIndex` ([3e8870c](https://github.com/thi-ng/umbrella/commit/3e8870c))
|
|
19
|
+
- store allocator in `ManagedIndex` due to Zig ArrayList defaulting to unmanaged in 0.15.1
|
|
20
|
+
and managed versions will be removed in future
|
|
21
|
+
- fix alignment param in `_wasm_allocate()`
|
|
22
|
+
|
|
23
|
+
## [2.4.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/wasm-api@2.4.0) (2025-09-01)
|
|
24
|
+
|
|
25
|
+
#### 🚀 Features
|
|
26
|
+
|
|
27
|
+
- update Zig build file for Zig v0.15.1 ([ddb2402](https://github.com/thi-ng/umbrella/commit/ddb2402))
|
|
28
|
+
- set Zig workspace version to 0.15.1
|
|
29
|
+
|
|
14
30
|
### [2.3.12](https://github.com/thi-ng/umbrella/tree/@thi.ng/wasm-api@2.3.12) (2025-03-11)
|
|
15
31
|
|
|
16
32
|
#### ♻️ Refactoring
|
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
- [API module auto-initialization](#api-module-auto-initialization)
|
|
23
23
|
- [Object indices & handles](#object-indices--handles)
|
|
24
24
|
- [Using the Zig build system](#using-the-zig-build-system)
|
|
25
|
-
- [Zig v0.
|
|
25
|
+
- [Zig v0.15 or newer](#zig-v015-or-newer)
|
|
26
26
|
- [Example projects](#example-projects)
|
|
27
27
|
- [Naming & structural conventions](#naming--structural-conventions)
|
|
28
28
|
- [Status](#status)
|
|
@@ -379,15 +379,13 @@ This package provides utilities to simplify using hybrid TS/Zig WASM API modules
|
|
|
379
379
|
which are distributed as NPM packages. Using these utils, a build file for Zig's
|
|
380
380
|
built-in build system is as simple as:
|
|
381
381
|
|
|
382
|
-
### Zig v0.
|
|
382
|
+
### Zig v0.15 or newer
|
|
383
383
|
|
|
384
|
-
**IMPORTANT:** Due to
|
|
385
|
-
|
|
386
|
-
[v0.
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
versions are still
|
|
390
|
-
included](https://github.com/thi-ng/umbrella/blob/develop/packages/wasm-api/zig))
|
|
384
|
+
**IMPORTANT:** Due to breaking syntax & build system changes in recent Zig
|
|
385
|
+
versions, older Zig versions than
|
|
386
|
+
[v0.15.1](https://ziglang.org/download/0.15.1/release-notes.html), are not
|
|
387
|
+
actively supported anymore (however, [build files for older versions are still
|
|
388
|
+
included](https://github.com/thi-ng/umbrella/blob/develop/packages/wasm-api/zig)).
|
|
391
389
|
|
|
392
390
|
Please see [bundled examples](#usage-examples) for more details...
|
|
393
391
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thi.ng/wasm-api",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.1",
|
|
4
4
|
"description": "Generic, modular, extensible API bridge and infrastructure for hybrid JS & WebAssembly projects",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./index.js",
|
|
@@ -42,17 +42,17 @@
|
|
|
42
42
|
"tool:tangle": "../../node_modules/.bin/tangle src/**/*.ts"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@thi.ng/api": "^8.12.
|
|
46
|
-
"@thi.ng/arrays": "^2.13.
|
|
47
|
-
"@thi.ng/checks": "^3.7.
|
|
48
|
-
"@thi.ng/errors": "^2.5.
|
|
49
|
-
"@thi.ng/hex": "^2.3.
|
|
50
|
-
"@thi.ng/idgen": "^2.2.
|
|
51
|
-
"@thi.ng/logger": "^3.1.
|
|
45
|
+
"@thi.ng/api": "^8.12.2",
|
|
46
|
+
"@thi.ng/arrays": "^2.13.11",
|
|
47
|
+
"@thi.ng/checks": "^3.7.18",
|
|
48
|
+
"@thi.ng/errors": "^2.5.42",
|
|
49
|
+
"@thi.ng/hex": "^2.3.80",
|
|
50
|
+
"@thi.ng/idgen": "^2.2.76",
|
|
51
|
+
"@thi.ng/logger": "^3.1.17"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"esbuild": "^0.25.
|
|
55
|
-
"typedoc": "^0.28.
|
|
54
|
+
"esbuild": "^0.25.9",
|
|
55
|
+
"typedoc": "^0.28.12",
|
|
56
56
|
"typescript": "^5.9.2"
|
|
57
57
|
},
|
|
58
58
|
"keywords": [
|
|
@@ -126,5 +126,5 @@
|
|
|
126
126
|
"tag": "wasm",
|
|
127
127
|
"year": 2022
|
|
128
128
|
},
|
|
129
|
-
"gitHead": "
|
|
129
|
+
"gitHead": "4ebea5f30d4e2d28a12dfb01b39533fdfda0ab43\n"
|
|
130
130
|
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
//! Build helpers for using modules/packages distributed via NPM
|
|
3
|
+
//! Intended for use with https://thi.ng/wasm-api and support packages
|
|
4
|
+
//!
|
|
5
|
+
//! This version of the script is only compatible with:
|
|
6
|
+
//! Zig v0.14.1
|
|
7
|
+
//!
|
|
8
|
+
//! Use other build.zig files in this same directory for older Zig versions
|
|
9
|
+
|
|
10
|
+
const std = @import("std");
|
|
11
|
+
const Build = std.Build;
|
|
12
|
+
|
|
13
|
+
/// Config options
|
|
14
|
+
pub const WasmLibOpts = struct {
|
|
15
|
+
/// Base path to common node_modules directory under which
|
|
16
|
+
/// all to be imported modules are located
|
|
17
|
+
base: []const u8 = "node_modules",
|
|
18
|
+
/// Relative path to root source file
|
|
19
|
+
root: []const u8 = "zig/main.zig",
|
|
20
|
+
/// Additional WASM API support modules.
|
|
21
|
+
/// Only need to specify custom/extra modules
|
|
22
|
+
/// `wasm-api` and `wasm-api-bindgen` are auto-added as dependency for all...
|
|
23
|
+
modules: ?[]const ModuleSpec = null,
|
|
24
|
+
/// Build mode override (else allows config via CLI args)
|
|
25
|
+
optimize: ?std.builtin.Mode = null,
|
|
26
|
+
/// Additional WASM target features, e.g. `.simd128` to enable SIMD
|
|
27
|
+
features: ?[]const std.Target.wasm.Feature = null,
|
|
28
|
+
/// Initial memory (in bytes, MUST be multiple of 0x10000)
|
|
29
|
+
initialMemory: ?u64 = null,
|
|
30
|
+
/// Max memory (in bytes, MUST be multiple of 0x10000)
|
|
31
|
+
maxMemory: ?u64 = null,
|
|
32
|
+
// If true, build will also generate docs
|
|
33
|
+
// docs: bool = false,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/// WASM API sub-module declaration
|
|
37
|
+
pub const ModuleSpec = struct {
|
|
38
|
+
/// module import name/ID
|
|
39
|
+
name: []const u8,
|
|
40
|
+
/// Relative path to configured base path
|
|
41
|
+
path: []const u8,
|
|
42
|
+
/// Module IDs of other modules this module depends on
|
|
43
|
+
/// Only need to specify custom/extra modules
|
|
44
|
+
/// `wasm-api` and `wasm-api-bindgen` are auto-added as dependency for all...
|
|
45
|
+
dependencies: ?[]const []const u8 = null,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const wasmapi = "wasm-api";
|
|
49
|
+
const wasmbind = "wasm-api-bindgen";
|
|
50
|
+
|
|
51
|
+
/// Creates and returns a build step to build a dynamic WASM library, configured
|
|
52
|
+
/// with given options and package declarations. The given package specs will be inserted
|
|
53
|
+
pub fn wasmLib(b: *Build, opts: WasmLibOpts) *Build.Step.Compile {
|
|
54
|
+
const optimize = if (opts.optimize) |m| m else b.standardOptimizeOption(.{});
|
|
55
|
+
const lib = b.addExecutable(.{
|
|
56
|
+
.name = "main",
|
|
57
|
+
.root_source_file = b.path(opts.root),
|
|
58
|
+
.target = b.resolveTargetQuery(
|
|
59
|
+
.{
|
|
60
|
+
.cpu_arch = .wasm32,
|
|
61
|
+
.os_tag = .freestanding,
|
|
62
|
+
.cpu_features_add = if (opts.features) |features| std.Target.wasm.featureSet(features) else std.Target.Cpu.Feature.Set.empty,
|
|
63
|
+
},
|
|
64
|
+
),
|
|
65
|
+
.optimize = optimize,
|
|
66
|
+
.strip = (optimize == .ReleaseSmall or optimize == .ReleaseFast),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// build flags
|
|
70
|
+
// https://github.com/ziglang/zig/pull/17815
|
|
71
|
+
lib.entry = .disabled;
|
|
72
|
+
lib.rdynamic = true;
|
|
73
|
+
lib.import_symbols = true;
|
|
74
|
+
lib.initial_memory = opts.initialMemory;
|
|
75
|
+
lib.max_memory = opts.maxMemory;
|
|
76
|
+
|
|
77
|
+
// add default dependencies
|
|
78
|
+
// https://ziggit.dev/t/what-happened-to-addmodule/2908/3
|
|
79
|
+
_ = lib.root_module.addImport(
|
|
80
|
+
wasmapi,
|
|
81
|
+
b.createModule(.{
|
|
82
|
+
.root_source_file = b.path(modulePath(b.allocator, opts.base, "@thi.ng/wasm-api/zig/lib.zig")),
|
|
83
|
+
}),
|
|
84
|
+
);
|
|
85
|
+
_ = lib.root_module.addImport(
|
|
86
|
+
wasmbind,
|
|
87
|
+
b.createModule(.{
|
|
88
|
+
.root_source_file = b.path(modulePath(b.allocator, opts.base, "@thi.ng/wasm-api-bindgen/zig/lib.zig")),
|
|
89
|
+
}),
|
|
90
|
+
);
|
|
91
|
+
if (opts.modules) |modules| {
|
|
92
|
+
for (modules) |mod| register(lib, mod, opts);
|
|
93
|
+
}
|
|
94
|
+
return lib;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/// Registers a single package and its deps (also injects core wasm-api deps)
|
|
98
|
+
pub fn register(lib: *Build.Step.Compile, mod: ModuleSpec, opts: WasmLibOpts) void {
|
|
99
|
+
const num = if (mod.dependencies) |ids| ids.len else 0;
|
|
100
|
+
var dpkgs = lib.step.owner.allocator.alloc(Build.Module.Import, num + 2) catch unreachable;
|
|
101
|
+
dpkgs[0] = if (lib.root_module.import_table.get(wasmapi)) |m| .{ .name = wasmapi, .module = m } else unreachable;
|
|
102
|
+
dpkgs[1] = if (lib.root_module.import_table.get(wasmbind)) |m| .{ .name = wasmbind, .module = m } else unreachable;
|
|
103
|
+
var i: usize = 2;
|
|
104
|
+
if (mod.dependencies) |ids| {
|
|
105
|
+
for (ids) |id| {
|
|
106
|
+
if (lib.root_module.import_table.get(id)) |m| {
|
|
107
|
+
if (isDuplicateId(dpkgs[0..i], id)) {
|
|
108
|
+
std.log.info("ignoring duplicate dependency: {s}", .{id});
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
dpkgs[i] = .{ .name = id, .module = m };
|
|
112
|
+
i += 1;
|
|
113
|
+
} else @panic("unknown dependency");
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
_ = lib.root_module.addImport(mod.name, lib.step.owner.createModule(.{
|
|
117
|
+
.root_source_file = lib.step.owner.path(modulePath(lib.step.owner.allocator, opts.base, mod.path)),
|
|
118
|
+
.imports = dpkgs[0..i],
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
fn isDuplicateId(deps: []Build.Module.Import, name: []const u8) bool {
|
|
123
|
+
for (deps) |d| {
|
|
124
|
+
if (std.mem.eql(u8, d.name, name)) return true;
|
|
125
|
+
}
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
fn modulePath(allocator: std.mem.Allocator, base: []const u8, path: []const u8) []u8 {
|
|
130
|
+
return std.fs.path.join(allocator, &.{ base, path }) catch unreachable;
|
|
131
|
+
}
|
package/zig/build.zig
CHANGED
|
@@ -22,7 +22,7 @@ pub const WasmLibOpts = struct {
|
|
|
22
22
|
/// `wasm-api` and `wasm-api-bindgen` are auto-added as dependency for all...
|
|
23
23
|
modules: ?[]const ModuleSpec = null,
|
|
24
24
|
/// Build mode override (else allows config via CLI args)
|
|
25
|
-
optimize: ?std.builtin.
|
|
25
|
+
optimize: ?std.builtin.OptimizeMode = null,
|
|
26
26
|
/// Additional WASM target features, e.g. `.simd128` to enable SIMD
|
|
27
27
|
features: ?[]const std.Target.wasm.Feature = null,
|
|
28
28
|
/// Initial memory (in bytes, MUST be multiple of 0x10000)
|
|
@@ -54,16 +54,18 @@ pub fn wasmLib(b: *Build, opts: WasmLibOpts) *Build.Step.Compile {
|
|
|
54
54
|
const optimize = if (opts.optimize) |m| m else b.standardOptimizeOption(.{});
|
|
55
55
|
const lib = b.addExecutable(.{
|
|
56
56
|
.name = "main",
|
|
57
|
-
.
|
|
58
|
-
|
|
59
|
-
.
|
|
60
|
-
.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
57
|
+
.root_module = b.createModule(.{
|
|
58
|
+
.root_source_file = b.path(opts.root),
|
|
59
|
+
.target = b.resolveTargetQuery(
|
|
60
|
+
.{
|
|
61
|
+
.cpu_arch = .wasm32,
|
|
62
|
+
.os_tag = .freestanding,
|
|
63
|
+
.cpu_features_add = if (opts.features) |features| std.Target.wasm.featureSet(features) else std.Target.Cpu.Feature.Set.empty,
|
|
64
|
+
},
|
|
65
|
+
),
|
|
66
|
+
.optimize = optimize,
|
|
67
|
+
.strip = (optimize == .ReleaseSmall or optimize == .ReleaseFast),
|
|
68
|
+
}),
|
|
67
69
|
});
|
|
68
70
|
|
|
69
71
|
// build flags
|
package/zig/lib.zig
CHANGED
|
@@ -52,7 +52,7 @@ pub inline fn allocator() ?std.mem.Allocator {
|
|
|
52
52
|
/// Note: For SIMD compatibility all allocations are aligned to 16 bytes
|
|
53
53
|
pub export fn _wasm_allocate(numBytes: usize) usize {
|
|
54
54
|
if (allocator()) |alloc| {
|
|
55
|
-
const mem = alloc.alignedAlloc(u8, 16, numBytes) catch return 0;
|
|
55
|
+
const mem = alloc.alignedAlloc(u8, .@"16", numBytes) catch return 0;
|
|
56
56
|
return @intFromPtr(mem.ptr);
|
|
57
57
|
}
|
|
58
58
|
return 0;
|
package/zig/managed-index.zig
CHANGED
|
@@ -24,6 +24,7 @@ pub fn ManagedIndex(comptime T: type, comptime I: type) type {
|
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
return struct {
|
|
27
|
+
allocator: Allocator,
|
|
27
28
|
/// backing array list
|
|
28
29
|
list: std.ArrayList(Item),
|
|
29
30
|
/// Index of the head of the implicitly stored list of free slots
|
|
@@ -37,22 +38,24 @@ pub fn ManagedIndex(comptime T: type, comptime I: type) type {
|
|
|
37
38
|
const Self = @This();
|
|
38
39
|
|
|
39
40
|
/// Initializes the underlying array list
|
|
40
|
-
pub fn init(
|
|
41
|
+
pub fn init(alloc: Allocator) Self {
|
|
41
42
|
return .{
|
|
42
|
-
.
|
|
43
|
+
.allocator = alloc,
|
|
44
|
+
.list = std.ArrayList(Item).empty,
|
|
43
45
|
};
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
/// Initializes the underlying array list with given initial capacity
|
|
47
|
-
pub fn initCapacity(
|
|
49
|
+
pub fn initCapacity(alloc: Allocator, num: usize) Allocator.Error!Self {
|
|
48
50
|
return .{
|
|
49
|
-
.
|
|
51
|
+
.allocator = alloc,
|
|
52
|
+
.list = try std.ArrayList(Item).initCapacity(alloc, num),
|
|
50
53
|
};
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
/// De-initializes the underlying array list
|
|
54
57
|
pub fn deinit(self: *Self) void {
|
|
55
|
-
self.list.deinit();
|
|
58
|
+
self.list.deinit(self.allocator);
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
/// Clears, frees & resets this list and its backing array list.
|
|
@@ -72,7 +75,7 @@ pub fn ManagedIndex(comptime T: type, comptime I: type) type {
|
|
|
72
75
|
return @intCast(id);
|
|
73
76
|
} else {
|
|
74
77
|
const id = self.list.items.len;
|
|
75
|
-
try self.list.append(.{ .value = item });
|
|
78
|
+
try self.list.append(self.allocator, .{ .value = item });
|
|
76
79
|
self.used += 1;
|
|
77
80
|
return @intCast(id);
|
|
78
81
|
}
|
|
@@ -179,53 +182,53 @@ test "ManagedIndex" {
|
|
|
179
182
|
const expect = std.testing.expect;
|
|
180
183
|
const expectEqualSlices = std.testing.expectEqualSlices;
|
|
181
184
|
|
|
182
|
-
var
|
|
183
|
-
defer
|
|
184
|
-
try expect(try
|
|
185
|
-
try expect(try
|
|
185
|
+
var index = ManagedIndex(f64, u8).init(allocator);
|
|
186
|
+
defer index.deinit();
|
|
187
|
+
try expect(try index.add(123) == 0);
|
|
188
|
+
try expect(try index.add(123) == 1);
|
|
186
189
|
|
|
187
|
-
try expect(
|
|
188
|
-
try expect(
|
|
189
|
-
try expect(
|
|
190
|
+
try expect(index.has(0));
|
|
191
|
+
try expect(index.has(1));
|
|
192
|
+
try expect(index.used == 2);
|
|
190
193
|
|
|
191
|
-
|
|
192
|
-
try expect(!
|
|
193
|
-
try expect(!
|
|
194
|
-
try expect(
|
|
194
|
+
index.remove(0);
|
|
195
|
+
try expect(!index.has(0));
|
|
196
|
+
try expect(!index.has(2));
|
|
197
|
+
try expect(index.used == 1);
|
|
195
198
|
|
|
196
|
-
try expect(try
|
|
197
|
-
try expect(try
|
|
198
|
-
try expect(
|
|
199
|
-
try expect(
|
|
199
|
+
try expect(try index.add(123) == 0);
|
|
200
|
+
try expect(try index.add(123) == 2);
|
|
201
|
+
try expect(index.has(2));
|
|
202
|
+
try expect(index.used == 3);
|
|
200
203
|
|
|
201
|
-
|
|
202
|
-
|
|
204
|
+
index.remove(1);
|
|
205
|
+
index.remove(2);
|
|
203
206
|
|
|
204
|
-
const ids = try
|
|
207
|
+
const ids = try index.freeIndicesAsSlice(allocator);
|
|
205
208
|
defer allocator.free(ids);
|
|
206
209
|
try expectEqualSlices(u8, &[_]u8{ 2, 1 }, ids);
|
|
207
210
|
|
|
208
|
-
try expect(try
|
|
209
|
-
|
|
210
|
-
try expect(try
|
|
211
|
-
try expect(try
|
|
212
|
-
try expect(
|
|
211
|
+
try expect(try index.add(202) == 2);
|
|
212
|
+
index.remove(0);
|
|
213
|
+
try expect(try index.add(200) == 0);
|
|
214
|
+
try expect(try index.add(201) == 1);
|
|
215
|
+
try expect(index.freeID == null);
|
|
213
216
|
|
|
214
|
-
var iter =
|
|
217
|
+
var iter = index.values();
|
|
215
218
|
try expect(if (iter.next()) |v| v == 200 else false);
|
|
216
219
|
try expect(if (iter.next()) |v| v == 201 else false);
|
|
217
220
|
try expect(if (iter.next()) |v| v == 202 else false);
|
|
218
221
|
try expect(iter.next() == null);
|
|
219
222
|
|
|
220
|
-
const slice = try
|
|
223
|
+
const slice = try index.valuesAsSlice(allocator);
|
|
221
224
|
defer allocator.free(slice);
|
|
222
225
|
try expectEqualSlices(f64, &[_]f64{ 200, 201, 202 }, slice);
|
|
223
226
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
+
index.remove(0);
|
|
228
|
+
index.remove(2);
|
|
229
|
+
index.remove(1);
|
|
227
230
|
|
|
228
|
-
const ids2 = try
|
|
231
|
+
const ids2 = try index.freeIndicesAsSlice(allocator);
|
|
229
232
|
defer allocator.free(ids2);
|
|
230
233
|
try expectEqualSlices(u8, &[_]u8{ 1, 2, 0 }, ids2);
|
|
231
234
|
}
|