react-native-impeller-platform 0.0.0-stage → 0.1.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.
- package/README.md +165 -2
- package/package.json +52 -4
- package/react-native.config.js +24 -0
- package/src/bin/engine-path.js +12 -0
- package/src/cli/index.js +20 -0
- package/src/cli/init.js +112 -0
- package/src/cli/run.js +73 -0
- package/src/externaltexture/index.js +58 -0
- package/src/index.js +12 -0
- package/src/metro-shims/index.js +382 -0
- package/src/metro-shims/shims/Alert.impeller.js +89 -0
- package/src/metro-shims/shims/BaseViewConfig.impeller.js +81 -0
- package/src/metro-shims/shims/Button.impeller.js +100 -0
- package/src/metro-shims/shims/Image.impeller.js +97 -0
- package/src/metro-shims/shims/Platform.impeller-as-ios.js +44 -0
- package/src/metro-shims/shims/Platform.impeller.js +53 -0
- package/src/metro-shims/shims/ReactNativeFeatureFlags.impeller.js +40 -0
- package/src/metro-shims/shims/ReactNativeStyleAttributes.impeller.js +20 -0
- package/src/metro-shims/shims/TextInputState.impeller.js +100 -0
- package/src/metro-shims/shims/ViewConfigIgnore.impeller.js +55 -0
- package/src/metro-shims/shims/reanimated/clock.js +41 -0
- package/src/metro-shims/shims/reanimated/component.js +83 -0
- package/src/metro-shims/shims/reanimated/css.js +193 -0
- package/src/metro-shims/shims/reanimated/easing.js +47 -0
- package/src/metro-shims/shims/reanimated/hooks.js +191 -0
- package/src/metro-shims/shims/reanimated/interpolate.js +133 -0
- package/src/metro-shims/shims/reanimated/values.js +264 -0
- package/src/metro-shims/shims/reanimated.impeller.js +90 -0
- package/src/platformview/index.js +23 -0
- package/src/resolve.js +238 -0
- package/src/selftest.js +123 -0
- package/src/storage/index.js +161 -0
- package/src/viewshot/index.js +98 -0
- package/src/worldraster/index.js +75 -0
- package/templates/app/README.md.tmpl +18 -0
- package/templates/app/_gitignore +1 -0
- package/templates/app/impeller.config.json.tmpl +11 -0
package/src/selftest.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self-test for the engine resolver.
|
|
3
|
+
*
|
|
4
|
+
* Runs as `npm test` INSIDE THE PUBLISHED PACKAGE, which is the point: it is
|
|
5
|
+
* the one check a user can run on the artifact they actually installed, on a
|
|
6
|
+
* machine we have never seen. So it must depend on nothing but this package —
|
|
7
|
+
* no dev dependencies, no test runner, no network, no engine binary.
|
|
8
|
+
*
|
|
9
|
+
* package.json declared this script for the whole of P1-P6 while the file did
|
|
10
|
+
* not exist; `npm test` died with MODULE_NOT_FOUND. tools/ci-release-meta.sh
|
|
11
|
+
* now fails the build when a declared entry point is missing.
|
|
12
|
+
*/
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
const assert = require('assert');
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const os = require('os');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
const {resolveEngine, hostTriple, packageNameFor, binaryFileName, SUPPORTED} =
|
|
21
|
+
require('./resolve');
|
|
22
|
+
|
|
23
|
+
let passed = 0;
|
|
24
|
+
function check(name, fn) {
|
|
25
|
+
try {
|
|
26
|
+
fn();
|
|
27
|
+
console.log(` ok ${name}`);
|
|
28
|
+
passed++;
|
|
29
|
+
} catch (e) {
|
|
30
|
+
console.error(` FAIL ${name}\n ${e.message}`);
|
|
31
|
+
process.exitCode = 1;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
console.log('react-native-impeller-platform selftest');
|
|
36
|
+
|
|
37
|
+
check('host triple is platform-arch', () => {
|
|
38
|
+
assert.strictEqual(hostTriple('linux', 'x64'), 'linux-x64');
|
|
39
|
+
assert.strictEqual(hostTriple('win32', 'x64'), 'win32-x64');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
check('engine package name is derived from the triple', () => {
|
|
43
|
+
assert.strictEqual(
|
|
44
|
+
packageNameFor('linux-x64'),
|
|
45
|
+
'@blbaker/react-native-impeller-platform-engine-linux-x64'
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
check('binary name is .exe only on win32', () => {
|
|
50
|
+
assert.strictEqual(binaryFileName('linux'), 'rn-impeller');
|
|
51
|
+
assert.strictEqual(binaryFileName('darwin'), 'rn-impeller');
|
|
52
|
+
assert.strictEqual(binaryFileName('win32'), 'rn-impeller.exe');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
check('every SUPPORTED triple names a real optionalDependency', () => {
|
|
56
|
+
// The defect this guards: a triple advertised as supported with no package
|
|
57
|
+
// published. npm SKIPS a missing optional dependency and exits 0, so the
|
|
58
|
+
// user gets a silent no-engine install and then an error instructing them to
|
|
59
|
+
// install something that does not exist.
|
|
60
|
+
const pkg = require('../package.json');
|
|
61
|
+
const opt = Object.keys(pkg.optionalDependencies || {});
|
|
62
|
+
for (const triple of SUPPORTED) {
|
|
63
|
+
assert.ok(
|
|
64
|
+
opt.includes(packageNameFor(triple)),
|
|
65
|
+
`${triple} is SUPPORTED but ${packageNameFor(triple)} is not an optionalDependency`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
check('every engine optionalDependency is pinned exactly, never a range', () => {
|
|
71
|
+
// The engine exposes an unstable C ABI. A ^ or ~ range lets npm resolve a
|
|
72
|
+
// newer engine against an older host, which crashes at runtime rather than
|
|
73
|
+
// failing to install.
|
|
74
|
+
const pkg = require('../package.json');
|
|
75
|
+
for (const [name, spec] of Object.entries(pkg.optionalDependencies || {})) {
|
|
76
|
+
assert.strictEqual(spec, pkg.version, `${name} is pinned '${spec}', expected '${pkg.version}'`);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
check('an unsupported triple is refused, without demanding the user build it', () => {
|
|
81
|
+
// ⛔ THIS TEST USED TO REQUIRE THE OPPOSITE.
|
|
82
|
+
// It asserted the error names IMPELLER_ENGINE_BINARY — i.e. it ENFORCED
|
|
83
|
+
// telling a consumer whose install just failed to go compile a C++ engine.
|
|
84
|
+
// A test can pin the wrong behaviour just as firmly as the right one; this
|
|
85
|
+
// one kept contributor workflow in a consumer error path.
|
|
86
|
+
assert.throws(
|
|
87
|
+
() => resolveEngine({platform: 'sunos', arch: 'sparc', env: {}}),
|
|
88
|
+
(e) =>
|
|
89
|
+
/does not support sunos-sparc yet/.test(e.message) &&
|
|
90
|
+
/Supported: /.test(e.message) &&
|
|
91
|
+
// The message must NOT hand a consumer a build instruction.
|
|
92
|
+
!/IMPELLER_ENGINE_BINARY/.test(e.message)
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
check('IMPELLER_ENGINE_BINARY overrides the installed package', () => {
|
|
97
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'rni-selftest-'));
|
|
98
|
+
const fake = path.join(tmp, 'rn-impeller');
|
|
99
|
+
fs.writeFileSync(fake, '');
|
|
100
|
+
try {
|
|
101
|
+
const r = resolveEngine({env: {IMPELLER_ENGINE_BINARY: fake}});
|
|
102
|
+
assert.strictEqual(r.source, 'env');
|
|
103
|
+
assert.strictEqual(r.binary, fake);
|
|
104
|
+
} finally {
|
|
105
|
+
fs.rmSync(tmp, {recursive: true, force: true});
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
check('a missing IMPELLER_ENGINE_BINARY fails loudly, not silently', () => {
|
|
110
|
+
// "Cannot find it" and "using the default" must never share an outcome: an
|
|
111
|
+
// override that silently falls back would run a DIFFERENT engine than the
|
|
112
|
+
// developer asked for, which is the hardest kind of bug to see.
|
|
113
|
+
assert.throws(
|
|
114
|
+
() => resolveEngine({env: {IMPELLER_ENGINE_BINARY: '/nonexistent/rn-impeller'}}),
|
|
115
|
+
/no file exists there/
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
console.log(
|
|
120
|
+
process.exitCode
|
|
121
|
+
? 'SELFTEST_FAIL'
|
|
122
|
+
: `SELFTEST_PASS (${passed} checks)`
|
|
123
|
+
);
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// ENG-04 shape B, the JS half. An AsyncStorage-shaped API composed on top of
|
|
3
|
+
// the engine's ONE file primitive.
|
|
4
|
+
//
|
|
5
|
+
// WHY THIS LIVES IN JS
|
|
6
|
+
// -------------------
|
|
7
|
+
// The engine vends AppStorage.readFile/writeFile/deleteFile/listFiles and
|
|
8
|
+
// nothing more. That is a deliberate boundary: persistence is not a renderer
|
|
9
|
+
// concern, and if the engine owned a key-value API instead, every later need
|
|
10
|
+
// (structured values, namespacing, encryption, migration) would be a new C++
|
|
11
|
+
// row against a surface frozen in a native release. Here it is a JS change.
|
|
12
|
+
//
|
|
13
|
+
// Same division as Alert.impeller.js: the engine owns the CAPABILITY, JS owns
|
|
14
|
+
// the SHAPE.
|
|
15
|
+
//
|
|
16
|
+
// ⛔ ASYNC ON PURPOSE, OVER A SYNCHRONOUS PRIMITIVE.
|
|
17
|
+
// The native calls are synchronous (small flat files, called from the JS
|
|
18
|
+
// thread). This wrapper is async anyway, because async is the shape every RN
|
|
19
|
+
// storage consumer expects and the one you cannot retrofit later without
|
|
20
|
+
// breaking every caller. If the native half ever moves to a thread pool, no
|
|
21
|
+
// consumer of this module changes.
|
|
22
|
+
|
|
23
|
+
const {TurboModuleRegistry} = require('react-native');
|
|
24
|
+
|
|
25
|
+
// ⚠️ get, NOT getEnforcing. An engine built before ENG-04 does not vend
|
|
26
|
+
// AppStorage, and a hard throw at import time would take the whole bundle down
|
|
27
|
+
// on a version mismatch rather than failing the one feature. The null check in
|
|
28
|
+
// mod() turns that into a rejected Promise from the call that needed it.
|
|
29
|
+
const Native = TurboModuleRegistry.get('AppStorage');
|
|
30
|
+
|
|
31
|
+
function mod() {
|
|
32
|
+
if (Native == null) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
'AppStorage native module is unavailable. The engine binary predates ' +
|
|
35
|
+
'ENG-04 (run `rn-impeller --list-modules` to confirm) — storage calls ' +
|
|
36
|
+
'cannot work until the engine is updated.',
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
return Native;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ⛔ KEY ENCODING IS NOT OPTIONAL AND IT IS NOT COSMETIC.
|
|
43
|
+
//
|
|
44
|
+
// The native side enforces a conservative allowlist ([A-Za-z0-9._-], one flat
|
|
45
|
+
// file per key, no nesting) because that is what keeps the container listable
|
|
46
|
+
// and the containment check simple. Real storage keys are not like that:
|
|
47
|
+
// '@myapp/user:profile' is an ordinary AsyncStorage key and would be REJECTED
|
|
48
|
+
// by the primitive.
|
|
49
|
+
//
|
|
50
|
+
// So keys are hex-encoded here. That is reversible, which listKeys() depends
|
|
51
|
+
// on, and it means no key can ever collide with another or escape the
|
|
52
|
+
// container -- a sanitising scheme (replace bad chars with '_') would silently
|
|
53
|
+
// map 'a/b' and 'a_b' onto ONE file, and the caller would never be told.
|
|
54
|
+
function encodeKey(key) {
|
|
55
|
+
if (typeof key !== 'string') {
|
|
56
|
+
throw new TypeError('storage key must be a string, got ' + typeof key);
|
|
57
|
+
}
|
|
58
|
+
let out = '';
|
|
59
|
+
for (let i = 0; i < key.length; i++) {
|
|
60
|
+
const code = key.charCodeAt(i);
|
|
61
|
+
// Encode the whole BMP code unit, not a byte: charCodeAt can return > 255
|
|
62
|
+
// for non-ASCII keys, and a 2-hex-digit assumption would truncate them
|
|
63
|
+
// into collisions ('\u0141' and 'A' both -> '41').
|
|
64
|
+
out += code.toString(16).padStart(4, '0');
|
|
65
|
+
}
|
|
66
|
+
return out + '.v1';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function decodeKey(name) {
|
|
70
|
+
if (!name.endsWith('.v1')) return null;
|
|
71
|
+
const body = name.slice(0, -3);
|
|
72
|
+
if (body.length % 4 !== 0) return null;
|
|
73
|
+
let out = '';
|
|
74
|
+
for (let i = 0; i < body.length; i += 4) {
|
|
75
|
+
const code = parseInt(body.slice(i, i + 4), 16);
|
|
76
|
+
if (Number.isNaN(code)) return null;
|
|
77
|
+
out += String.fromCharCode(code);
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// A missing key resolves to null, never to ''. Those are different facts --
|
|
83
|
+
// "nothing stored" vs "an empty string was stored" -- and the native primitive
|
|
84
|
+
// preserves the distinction specifically so this layer can too. Collapsing
|
|
85
|
+
// them would make removeItem() indistinguishable from setItem(k, '').
|
|
86
|
+
async function getItem(key) {
|
|
87
|
+
return mod().readFile(encodeKey(key));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function setItem(key, value) {
|
|
91
|
+
if (typeof value !== 'string') {
|
|
92
|
+
throw new TypeError(
|
|
93
|
+
'storage values must be strings (stringify first), got ' + typeof value,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
const ok = mod().writeFile(encodeKey(key), value);
|
|
97
|
+
if (!ok) {
|
|
98
|
+
// The primitive returns false for a rejected key, a full disk, or a failed
|
|
99
|
+
// atomic rename. It does not distinguish them, and neither can this layer
|
|
100
|
+
// honestly, so the error says what is known rather than guessing.
|
|
101
|
+
throw new Error('storage write failed for key: ' + key);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Idempotent, matching the native half: removing an absent key is success, not
|
|
106
|
+
// an error, because that is the state the caller asked for.
|
|
107
|
+
async function removeItem(key) {
|
|
108
|
+
const ok = mod().deleteFile(encodeKey(key));
|
|
109
|
+
if (!ok) throw new Error('storage delete failed for key: ' + key);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Enumeration comes from the DIRECTORY, not from an index file this module
|
|
113
|
+
// maintains. An index is a second source of truth that drifts the moment a
|
|
114
|
+
// write succeeds and the index update does not.
|
|
115
|
+
async function getAllKeys() {
|
|
116
|
+
const names = mod().listFiles();
|
|
117
|
+
const keys = [];
|
|
118
|
+
for (const name of names) {
|
|
119
|
+
const decoded = decodeKey(name);
|
|
120
|
+
// Skip anything this module did not write. A foreign file in the container
|
|
121
|
+
// is not an error and must not break enumeration for everything else.
|
|
122
|
+
if (decoded !== null) keys.push(decoded);
|
|
123
|
+
}
|
|
124
|
+
return keys;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function clear() {
|
|
128
|
+
const keys = await getAllKeys();
|
|
129
|
+
for (const key of keys) {
|
|
130
|
+
await removeItem(key);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function multiGet(keys) {
|
|
135
|
+
const out = [];
|
|
136
|
+
for (const key of keys) {
|
|
137
|
+
out.push([key, await getItem(key)]);
|
|
138
|
+
}
|
|
139
|
+
return out;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function multiSet(pairs) {
|
|
143
|
+
for (const [key, value] of pairs) {
|
|
144
|
+
await setItem(key, value);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
module.exports = {
|
|
149
|
+
getItem,
|
|
150
|
+
setItem,
|
|
151
|
+
removeItem,
|
|
152
|
+
getAllKeys,
|
|
153
|
+
clear,
|
|
154
|
+
multiGet,
|
|
155
|
+
multiSet,
|
|
156
|
+
// Exported for the gate: it asserts on the encoding round trip directly
|
|
157
|
+
// rather than inferring it from a get/set pair, so an encoding bug and a
|
|
158
|
+
// storage bug cannot be confused for each other.
|
|
159
|
+
encodeKey,
|
|
160
|
+
decodeKey,
|
|
161
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// FP-43 — react-native-view-shot's JS API on the engine's RNViewShot module.
|
|
3
|
+
//
|
|
4
|
+
// The engine implements view-shot's native module (captureRef, captureScreen,
|
|
5
|
+
// releaseCapture), so an app that depends on react-native-view-shot works as
|
|
6
|
+
// is; this file is the same surface for apps that do not. The snapshot renders
|
|
7
|
+
// the view's subtree into an offscreen on the raster thread, the png/jpg is
|
|
8
|
+
// encoded off the JS thread, and the Promise resolves on the JS thread.
|
|
9
|
+
//
|
|
10
|
+
// options: {format: 'png' | 'jpg', quality: 0..1, result: 'tmpfile' | 'base64'
|
|
11
|
+
// | 'data-uri', width, height (output pixels), fileName}.
|
|
12
|
+
|
|
13
|
+
const {TurboModuleRegistry, findNodeHandle} = require('react-native');
|
|
14
|
+
|
|
15
|
+
const Native = TurboModuleRegistry.get('RNViewShot');
|
|
16
|
+
|
|
17
|
+
function mod() {
|
|
18
|
+
if (Native == null) {
|
|
19
|
+
throw new Error(
|
|
20
|
+
'RNViewShot native module is unavailable. The engine binary predates FP-43: ' +
|
|
21
|
+
'view snapshots cannot work until the engine is updated.',
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
return Native;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const DEFAULTS = {format: 'png', quality: 1, result: 'tmpfile'};
|
|
28
|
+
|
|
29
|
+
// view-shot's validateOptions: defaults filled in, bad values reported.
|
|
30
|
+
function validateOptions(input) {
|
|
31
|
+
const options = {...DEFAULTS, ...(input || {})};
|
|
32
|
+
const errors = [];
|
|
33
|
+
if ('width' in options && (typeof options.width !== 'number' || options.width <= 0)) {
|
|
34
|
+
errors.push('option width should be a positive number');
|
|
35
|
+
delete options.width;
|
|
36
|
+
}
|
|
37
|
+
if ('height' in options && (typeof options.height !== 'number' || options.height <= 0)) {
|
|
38
|
+
errors.push('option height should be a positive number');
|
|
39
|
+
delete options.height;
|
|
40
|
+
}
|
|
41
|
+
if (typeof options.quality !== 'number' || options.quality < 0 || options.quality > 1) {
|
|
42
|
+
errors.push('option quality should be a number between 0.0 and 1.0');
|
|
43
|
+
options.quality = DEFAULTS.quality;
|
|
44
|
+
}
|
|
45
|
+
if (options.format === 'jpeg') options.format = 'jpg';
|
|
46
|
+
if (options.format !== 'png' && options.format !== 'jpg') {
|
|
47
|
+
errors.push(`option format '${options.format}' is not in valid formats: png | jpg`);
|
|
48
|
+
options.format = DEFAULTS.format;
|
|
49
|
+
}
|
|
50
|
+
if (!['tmpfile', 'base64', 'data-uri'].includes(options.result)) {
|
|
51
|
+
errors.push(`option result '${options.result}' is not in valid results: tmpfile | base64 | data-uri`);
|
|
52
|
+
options.result = DEFAULTS.result;
|
|
53
|
+
}
|
|
54
|
+
return {options, errors};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function toTag(view) {
|
|
58
|
+
if (view && typeof view === 'object' && 'current' in view) view = view.current;
|
|
59
|
+
if (typeof view === 'number') return view;
|
|
60
|
+
const tag = view == null ? null : findNodeHandle(view);
|
|
61
|
+
if (tag == null) throw new Error('findNodeHandle failed to resolve view=' + String(view));
|
|
62
|
+
return tag;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Resolves with a file:// uri, base64 or a data uri (options.result).
|
|
66
|
+
function captureRef(view, optionsObject) {
|
|
67
|
+
let tag;
|
|
68
|
+
try {
|
|
69
|
+
tag = toTag(view);
|
|
70
|
+
} catch (e) {
|
|
71
|
+
return Promise.reject(e);
|
|
72
|
+
}
|
|
73
|
+
const {options, errors} = validateOptions(optionsObject);
|
|
74
|
+
if (__DEV__ && errors.length > 0) {
|
|
75
|
+
console.warn('react-native-view-shot captureRef options: ' + errors.join(', '));
|
|
76
|
+
}
|
|
77
|
+
return mod().captureRef(tag, options);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function captureScreen(optionsObject) {
|
|
81
|
+
const {options, errors} = validateOptions(optionsObject);
|
|
82
|
+
if (__DEV__ && errors.length > 0) {
|
|
83
|
+
console.warn('react-native-view-shot captureScreen options: ' + errors.join(', '));
|
|
84
|
+
}
|
|
85
|
+
return mod().captureScreen(options);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Deletes a tmpfile capture.
|
|
89
|
+
function releaseCapture(uri) {
|
|
90
|
+
if (typeof uri !== 'string') return;
|
|
91
|
+
mod().releaseCapture(uri);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function isSupported() {
|
|
95
|
+
return Native != null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = {captureRef, captureScreen, releaseCapture, isSupported};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// ENG-09 — WorldRaster JS shim.
|
|
3
|
+
//
|
|
4
|
+
// The engine vends ONE primitive: WorldRaster.setPixels(id, rgba, w, h),
|
|
5
|
+
// synchronous, throwing on a mismatched buffer. This file is a thin,
|
|
6
|
+
// defensive wrapper -- same `get` (not `getEnforcing`) pattern as
|
|
7
|
+
// package/src/storage/index.js, so an engine built before ENG-09 fails the
|
|
8
|
+
// one call site that needs WorldRaster rather than taking the whole bundle
|
|
9
|
+
// down at import.
|
|
10
|
+
|
|
11
|
+
const {TurboModuleRegistry} = require('react-native');
|
|
12
|
+
|
|
13
|
+
const Native = TurboModuleRegistry.get('WorldRaster');
|
|
14
|
+
|
|
15
|
+
function mod() {
|
|
16
|
+
if (Native == null) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
'WorldRaster native module is unavailable. The engine binary predates ' +
|
|
19
|
+
'ENG-09 — world raster calls cannot work until the engine is updated.',
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
return Native;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function isSupported() {
|
|
26
|
+
return Native != null && mod().isSupported();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// id: string — must match the `name` prop set on the <world> host component
|
|
30
|
+
// this buffer is meant for. rgba: ArrayBuffer of exactly w*h*4 bytes,
|
|
31
|
+
// tightly packed RGBA8, row-major, top-to-bottom (the engine performs the
|
|
32
|
+
// Y-flip at paint time; callers never flip).
|
|
33
|
+
function setPixels(id, rgba, w, h) {
|
|
34
|
+
mod().setPixels(id, rgba, w, h);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ENG-10 — GPU sprite compositing.
|
|
38
|
+
//
|
|
39
|
+
// registerSprite(textureId, rgba, w, h): upload ONE atlas entry, once.
|
|
40
|
+
// rgba: ArrayBuffer of exactly w*h*4 bytes, STRAIGHT (non-premultiplied)
|
|
41
|
+
// RGBA8, top-to-bottom — canvas ImageData layout. Re-registering the same
|
|
42
|
+
// textureId replaces it.
|
|
43
|
+
function registerSprite(textureId, rgba, w, h) {
|
|
44
|
+
mod().registerSprite(textureId, rgba, w, h);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// drawSprites(id, sprites): per frame. `id` is the <world name> to composite
|
|
48
|
+
// over; sprites is an Array<{textureId, x, y, w, h, alpha?}> in layout points,
|
|
49
|
+
// BACK-TO-FRONT (list order is paint order). Throws on an unregistered
|
|
50
|
+
// textureId. Pass [] to clear.
|
|
51
|
+
function drawSprites(id, sprites) {
|
|
52
|
+
mod().drawSprites(id, sprites);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ENG-21 — setSampling(id, mode): how <world name=id> samples its setPixels
|
|
56
|
+
// terrain and every sprite drawn on it. mode 'nearest' keeps pixel-art edges
|
|
57
|
+
// hard at an integer scale; 'linear' (the default) blends neighbouring texels.
|
|
58
|
+
// Throws on any other mode. Engines built before ENG-21 lack the method; call
|
|
59
|
+
// isSamplingSupported() first if the app must run on them.
|
|
60
|
+
function setSampling(id, mode) {
|
|
61
|
+
mod().setSampling(id, mode);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function isSamplingSupported() {
|
|
65
|
+
return Native != null && typeof Native.setSampling === 'function';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = {
|
|
69
|
+
isSupported,
|
|
70
|
+
isSamplingSupported,
|
|
71
|
+
setPixels,
|
|
72
|
+
registerSprite,
|
|
73
|
+
drawSprites,
|
|
74
|
+
setSampling,
|
|
75
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# impeller/
|
|
2
|
+
|
|
3
|
+
Scaffolded by `npx react-native init-impeller` for **{{displayName}}**.
|
|
4
|
+
|
|
5
|
+
- `impeller.config.json` — window + app identity. The app name registered by the
|
|
6
|
+
engine is `{{appName}}`, taken from your `app.json`; the engine runs the same
|
|
7
|
+
name it registers.
|
|
8
|
+
- `build/` — Metro output for `--platform impeller`. Safe to delete; regenerated
|
|
9
|
+
by `run-impeller`. Add it to `.gitignore`.
|
|
10
|
+
|
|
11
|
+
The engine binary is **not** here. It is installed from npm as
|
|
12
|
+
`@blbaker/react-native-impeller-platform-engine-<platform>-<arch>` (an optional dependency of
|
|
13
|
+
`react-native-impeller-platform`) and located at runtime by the resolver. Set
|
|
14
|
+
`IMPELLER_ENGINE_BINARY` to point at a locally built engine instead.
|
|
15
|
+
|
|
16
|
+
Run it:
|
|
17
|
+
|
|
18
|
+
npx react-native run-impeller
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
build/
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"//": "Scaffolded by init-impeller. App identity is derived from your app.json --",
|
|
3
|
+
"//2": "nothing here is hardcoded to a sample app, and no path is CWD-relative.",
|
|
4
|
+
"appName": "{{appName}}",
|
|
5
|
+
"displayName": "{{displayName}}",
|
|
6
|
+
"window": {
|
|
7
|
+
"width": 1024,
|
|
8
|
+
"height": 768,
|
|
9
|
+
"title": "{{displayName}}"
|
|
10
|
+
}
|
|
11
|
+
}
|