mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-15 03:21:35 +08:00
[flutter] Add web_ffi fork, fix .wasm/.js size
The fork is required as Emscripten produces a .wasm file that the upstream web_ffi can't parse correctly to extract exported symbols.
This commit is contained in:
parent
fc9242bd22
commit
934e374724
@ -104,6 +104,7 @@ void DefaultSpineExtension::_free(void *mem, const char *file, int line) {
|
||||
}
|
||||
|
||||
char *DefaultSpineExtension::_readFile(const String &path, int *length) {
|
||||
#ifndef __EMSCRIPTEN__
|
||||
char *data;
|
||||
FILE *file = fopen(path.buffer(), "rb");
|
||||
if (!file) return 0;
|
||||
@ -117,6 +118,9 @@ char *DefaultSpineExtension::_readFile(const String &path, int *length) {
|
||||
fclose(file);
|
||||
|
||||
return data;
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
DefaultSpineExtension::DefaultSpineExtension() : SpineExtension() {
|
||||
|
||||
@ -14,6 +14,21 @@ In order to distribute your software containing the Spine Runtimes to others tha
|
||||
|
||||
For the official legal terms governing the Spine Runtimes, please read the [Spine Runtimes License Agreement](http://esotericsoftware.com/spine-runtimes-license) and Section 2 of the [Spine Editor License Agreement](http://esotericsoftware.com/spine-editor-license#s2).
|
||||
|
||||
### Licensing web_ffi
|
||||
spine-flutter includes a modified fork of [https://github.com/EPNW/web_ffi](web_ffi) by Eric Prokop und Nils Wieler Hard- und Softwareentwicklung GbR, which is licensed under the [BSD 2-Clause "Simplified" License](https://github.com/EPNW/web_ffi/blob/master/LICENSE).
|
||||
|
||||
```
|
||||
Copyright 2021 Eric Prokop und Nils Wieler Hard- und Softwareentwicklung GbR
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
```
|
||||
|
||||
## Spine version
|
||||
|
||||
spine-flutter works with data exported from Spine 4.1.xx.
|
||||
@ -37,5 +52,3 @@ The example in this repository is directly depending on the spine-flutter source
|
||||
Once installed, run the `setup.sh` script in the `spine-flutter` folder. This will copy [spine-cpp](../spine-cpp) to the appropriate locations.
|
||||
|
||||
You can then open `spine-flutter` in an IDE or editor of your choice that supports Flutter, like [IntelliJ IDEA/Android Studio](https://docs.flutter.dev/get-started/editor?tab=androidstudio) or [Visual Studio Code](https://docs.flutter.dev/get-started/editor?tab=vscode) to inspect and run the example. Alternatively, you can run the example from the [command line](https://docs.flutter.dev/get-started/test-drive?tab=terminal).
|
||||
|
||||
|
||||
@ -172,13 +172,6 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
web_ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_ffi
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.7.2"
|
||||
sdks:
|
||||
dart: ">=2.17.6 <3.0.0"
|
||||
flutter: ">=2.11.0"
|
||||
|
||||
@ -1 +1 @@
|
||||
export 'dart:ffi' if (dart.library.html) 'package:web_ffi/web_ffi.dart';
|
||||
export 'dart:ffi' if (dart.library.html) 'web_ffi/web_ffi.dart';
|
||||
@ -1,8 +1,8 @@
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:inject_js/inject_js.dart' as js;
|
||||
import 'package:spine_flutter/ffi_utf8.dart';
|
||||
import 'package:web_ffi/web_ffi.dart';
|
||||
import 'package:web_ffi/web_ffi_modules.dart';
|
||||
import 'web_ffi/web_ffi.dart';
|
||||
import 'web_ffi/web_ffi_modules.dart';
|
||||
import 'spine_flutter_bindings_generated.dart';
|
||||
|
||||
Module? _module;
|
||||
|
||||
284
spine-flutter/lib/web_ffi/ffi/extensions.dart
Executable file
284
spine-flutter/lib/web_ffi/ffi/extensions.dart
Executable file
@ -0,0 +1,284 @@
|
||||
import 'dart:typed_data';
|
||||
import 'types.dart';
|
||||
|
||||
import '../modules/memory.dart';
|
||||
import '../modules/module.dart';
|
||||
import '../internal/marshaller.dart';
|
||||
|
||||
import '../web_ffi_meta.dart';
|
||||
|
||||
/// Extension on [Pointer] specialized for the type argument [NativeFunction].
|
||||
extension NativeFunctionPointer<NF extends Function>
|
||||
on Pointer<NativeFunction<NF>> {
|
||||
/// Convert to Dart function, automatically marshalling the arguments and return value.
|
||||
///
|
||||
/// There are several rules that apply for the return type of `DF`, see
|
||||
/// the list of [allowed return types](https://github.com/EPNW/web_ffi/blob/master/return_types.md).
|
||||
/// If marshalling failes, a [MarshallingException] is thrown.
|
||||
///
|
||||
/// If this is called on a pointer that does not point to a function,
|
||||
/// a [ArgumentError](https://api.dart.dev/stable/dart-core/ArgumentError-class.html) is thrown.
|
||||
DF asFunction<DF extends Function>() {
|
||||
WasmSymbol symbol = symbolByAddress(boundMemory, address);
|
||||
if (symbol is FunctionDescription) {
|
||||
return marshall<NF, DF>(symbol.function, boundMemory);
|
||||
} else {
|
||||
throw ArgumentError(
|
||||
'No function at address $address was found (but a global symbol)!');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension DynamicLibraryExtension on DynamicLibrary {
|
||||
/// Helper that combines lookup and cast to a Dart function.
|
||||
///
|
||||
/// This simply calles [DynamicLibrary.lookup] and [NativeFunctionPointer.asFunction]
|
||||
/// internally, so see this two methods for additional insights.
|
||||
F lookupFunction<T extends Function, F extends Function>(String name) =>
|
||||
this.lookup<NativeFunction<T>>(name).asFunction<F>();
|
||||
}
|
||||
|
||||
/// Extension on [Allocator] to provide allocation with [NativeType].
|
||||
extension AllocatorAlloc on Allocator {
|
||||
/// Allocates `sizeOf<T>() * count` bytes of memory using [Allocator.allocate].
|
||||
///
|
||||
/// Since this calls [sizeOf<T>] internally, an exception will be thrown if this
|
||||
/// method is called with an @[unsized] type or before [Memory.init] was called.
|
||||
Pointer<T> call<T extends NativeType>([int count = 1]) =>
|
||||
allocate(sizeOf<T>() * count);
|
||||
}
|
||||
|
||||
/// Extension on [Pointer] specialized for the type argument [Float].
|
||||
extension FloatPointer on Pointer<Float> {
|
||||
/// The float at address.
|
||||
double get value => this[0];
|
||||
void set value(double value) => this[0] = value;
|
||||
|
||||
/// Creates a typed list view backed by memory in the address space.
|
||||
///
|
||||
/// The returned view will allow access to the memory range
|
||||
/// from address to `address + size * length`.
|
||||
Float32List asTypedList(int length) =>
|
||||
boundMemory.buffer.asFloat32List(address, length);
|
||||
|
||||
/// The float at `address + size * index`.
|
||||
double operator [](int index) =>
|
||||
viewSingle(index).getFloat32(0, Memory.endianess);
|
||||
void operator []=(int index, double value) =>
|
||||
viewSingle(index).setFloat32(0, value, Memory.endianess);
|
||||
}
|
||||
|
||||
/// Extension on [Pointer] specialized for the type argument [Double].
|
||||
extension DoublePointer on Pointer<Double> {
|
||||
/// The double at address.
|
||||
double get value => this[0];
|
||||
void set value(double value) => this[0] = value;
|
||||
|
||||
/// Creates a typed list view backed by memory in the address space.
|
||||
///
|
||||
/// The returned view will allow access to the memory range
|
||||
/// from address to `address + size * length`.
|
||||
Float64List asTypedList(int length) =>
|
||||
boundMemory.buffer.asFloat64List(address, length);
|
||||
|
||||
/// The double at `address + size * index`.
|
||||
double operator [](int index) =>
|
||||
viewSingle(index).getFloat64(0, Memory.endianess);
|
||||
void operator []=(int index, double value) =>
|
||||
viewSingle(index).setFloat64(0, value, Memory.endianess);
|
||||
}
|
||||
|
||||
/// Extension on [Pointer] specialized for the type argument [Int8].
|
||||
extension Int8Pointer on Pointer<Int8> {
|
||||
/// The 8-bit integer at `address`.
|
||||
int get value => this[0];
|
||||
void set value(int value) => this[0] = value;
|
||||
|
||||
/// Creates a typed list view backed by memory in the address space.
|
||||
///
|
||||
/// The returned view will allow access to the memory range
|
||||
/// from address to `address + size * length`.
|
||||
Int8List asTypedList(int length) =>
|
||||
boundMemory.buffer.asInt8List(address, length);
|
||||
|
||||
/// The 8-bit integer at `address + size * index`.
|
||||
int operator [](int index) => viewSingle(index).getInt8(0);
|
||||
void operator []=(int index, int value) =>
|
||||
viewSingle(index).setInt8(0, value);
|
||||
}
|
||||
|
||||
/// Extension on [Pointer] specialized for the type argument [Int16].
|
||||
extension Int16Pointer on Pointer<Int16> {
|
||||
/// The 16-bit integer at `address`.
|
||||
int get value => this[0];
|
||||
void set value(int value) => this[0] = value;
|
||||
|
||||
/// Creates a typed list view backed by memory in the address space.
|
||||
///
|
||||
/// The returned view will allow access to the memory range
|
||||
/// from address to `address + size * length`.
|
||||
Int16List asTypedList(int length) =>
|
||||
boundMemory.buffer.asInt16List(address, length);
|
||||
|
||||
/// The 16-bit integer at `address + size * index`.
|
||||
int operator [](int index) => viewSingle(index).getInt16(0, Memory.endianess);
|
||||
void operator []=(int index, int value) =>
|
||||
viewSingle(index).setInt16(0, value, Memory.endianess);
|
||||
}
|
||||
|
||||
/// Extension on [Pointer] specialized for the type argument [Int32].
|
||||
extension Int32Pointer on Pointer<Int32> {
|
||||
/// The 32-bit integer at `address`.
|
||||
int get value => this[0];
|
||||
void set value(int value) => this[0] = value;
|
||||
|
||||
/// Creates a typed list view backed by memory in the address space.
|
||||
///
|
||||
/// The returned view will allow access to the memory range
|
||||
/// from address to `address + size * length`.
|
||||
Int32List asTypedList(int length) =>
|
||||
boundMemory.buffer.asInt32List(address, length);
|
||||
|
||||
/// The 32-bit integer at `address + size * index`.
|
||||
int operator [](int index) => viewSingle(index).getInt32(0, Memory.endianess);
|
||||
void operator []=(int index, int value) =>
|
||||
viewSingle(index).setInt32(0, value, Memory.endianess);
|
||||
}
|
||||
|
||||
/// Extension on [Pointer] specialized for the type argument [Int64].
|
||||
extension Int64Pointer on Pointer<Int64> {
|
||||
/// The 64-bit integer at `address`.
|
||||
int get value => this[0];
|
||||
void set value(int value) => this[0] = value;
|
||||
|
||||
/// Creates a typed list view backed by memory in the address space.
|
||||
///
|
||||
/// The returned view will allow access to the memory range
|
||||
/// from address to `address + size * length`.
|
||||
Int64List asTypedList(int length) =>
|
||||
boundMemory.buffer.asInt64List(address, length);
|
||||
|
||||
/// The 64-bit integer at `address + size * index`.
|
||||
int operator [](int index) => viewSingle(index).getInt64(0, Memory.endianess);
|
||||
void operator []=(int index, int value) =>
|
||||
viewSingle(index).setInt64(0, value, Memory.endianess);
|
||||
}
|
||||
|
||||
/// Extension on [Pointer] specialized for the type argument [Uint8].
|
||||
extension Uint8Pointer on Pointer<Uint8> {
|
||||
/// The 8-bit unsigned integer at `address`.
|
||||
int get value => this[0];
|
||||
void set value(int value) => this[0] = value;
|
||||
|
||||
/// Creates a typed list view backed by memory in the address space.
|
||||
///
|
||||
/// The returned view will allow access to the memory range
|
||||
/// from address to `address + size * length`.
|
||||
Uint8List asTypedList(int length) =>
|
||||
boundMemory.buffer.asUint8List(address, length);
|
||||
|
||||
/// The 8-bit unsigned integer at `address + size * index`.
|
||||
int operator [](int index) => viewSingle(index).getUint8(0);
|
||||
void operator []=(int index, int value) =>
|
||||
viewSingle(index).setUint8(0, value);
|
||||
}
|
||||
|
||||
/// Extension on [Pointer] specialized for the type argument [Uint16].
|
||||
extension Uint16Pointer on Pointer<Uint16> {
|
||||
/// The 16-bit unsigned integer at `address`.
|
||||
int get value => this[0];
|
||||
void set value(int value) => this[0] = value;
|
||||
|
||||
/// Creates a typed list view backed by memory in the address space.
|
||||
///
|
||||
/// The returned view will allow access to the memory range
|
||||
/// from address to `address + size * length`.
|
||||
Uint16List asTypedList(int length) =>
|
||||
boundMemory.buffer.asUint16List(address, length);
|
||||
|
||||
/// The 16-bit unsigned integer at `address + size * index`.
|
||||
int operator [](int index) =>
|
||||
viewSingle(index).getUint16(0, Memory.endianess);
|
||||
void operator []=(int index, int value) =>
|
||||
viewSingle(index).setUint16(0, value, Memory.endianess);
|
||||
}
|
||||
|
||||
/// Extension on [Pointer] specialized for the type argument [Uint32].
|
||||
extension Uint32Pointer on Pointer<Uint32> {
|
||||
/// The 32-bit unsigned integer at `address`.
|
||||
int get value => this[0];
|
||||
void set value(int value) => this[0] = value;
|
||||
|
||||
/// Creates a typed list view backed by memory in the address space.
|
||||
///
|
||||
/// The returned view will allow access to the memory range
|
||||
/// from address to `address + size * length`.
|
||||
Uint32List asTypedList(int length) =>
|
||||
boundMemory.buffer.asUint32List(address, length);
|
||||
|
||||
/// The 32-bit unsigned integer at `address + size * index`.
|
||||
int operator [](int index) =>
|
||||
viewSingle(index).getUint32(0, Memory.endianess);
|
||||
void operator []=(int index, int value) =>
|
||||
viewSingle(index).setUint32(0, value, Memory.endianess);
|
||||
}
|
||||
|
||||
/// Extension on [Pointer] specialized for the type argument [Uint64].
|
||||
extension Uint64Pointer on Pointer<Uint64> {
|
||||
/// The 64-bit unsigned integer at `address`.
|
||||
int get value => this[0];
|
||||
void set value(int value) => this[0] = value;
|
||||
|
||||
/// Creates a typed list view backed by memory in the address space.
|
||||
///
|
||||
/// The returned view will allow access to the memory range
|
||||
/// from address to `address + size * length`.
|
||||
Uint64List asTypedList(int length) =>
|
||||
boundMemory.buffer.asUint64List(address, length);
|
||||
|
||||
/// The 64-bit unsigned integer at `address + size * index`.
|
||||
int operator [](int index) =>
|
||||
viewSingle(index).getUint64(0, Memory.endianess);
|
||||
void operator []=(int index, int value) =>
|
||||
viewSingle(index).setUint64(0, value, Memory.endianess);
|
||||
}
|
||||
|
||||
/// Extension on [Pointer] specialized for the type argument [IntPtr].
|
||||
extension IntPtrPointer on Pointer<IntPtr> {
|
||||
/// The 32-bit or 64-bit value at `address`.
|
||||
int get value => this[0];
|
||||
void set value(int value) => this[0] = value;
|
||||
|
||||
/// Returns `true` if the size of a pointer is 64-bit, `false` otherwise.
|
||||
@extra
|
||||
bool get is64Bit => size == 8;
|
||||
|
||||
/// The 32-bit or 64-bit integer at `address + size * index`.
|
||||
int operator [](int index) => is64Bit
|
||||
? viewSingle(index).getUint64(0, Memory.endianess)
|
||||
: viewSingle(index).getUint32(0, Memory.endianess);
|
||||
void operator []=(int index, int value) => is64Bit
|
||||
? viewSingle(index).setUint64(0, value, Memory.endianess)
|
||||
: viewSingle(index).setUint32(0, value, Memory.endianess);
|
||||
}
|
||||
|
||||
/// Extension on [Pointer] specialized for the type argument [Pointer].
|
||||
extension PointerPointer<T extends NativeType> on Pointer<Pointer<T>> {
|
||||
/// The pointer at `address`.
|
||||
Pointer<T> get value => this[0];
|
||||
void set value(Pointer<T> value) => this[0] = value;
|
||||
|
||||
/// Returns `true` if the size of a pointer is 64-bit, `false` otherwise.
|
||||
@extra
|
||||
bool get is64Bit => size == 8;
|
||||
|
||||
/// The pointer at `address + size * index`.
|
||||
Pointer<T> operator [](int index) => new Pointer<T>.fromAddress(
|
||||
is64Bit
|
||||
? viewSingle(index).getUint64(0, Memory.endianess)
|
||||
: viewSingle(index).getUint32(0, Memory.endianess),
|
||||
boundMemory);
|
||||
void operator []=(int index, Pointer<T> value) => is64Bit
|
||||
? viewSingle(index).setUint64(0, value.address, Memory.endianess)
|
||||
: viewSingle(index).setUint32(0, value.address, Memory.endianess);
|
||||
}
|
||||
326
spine-flutter/lib/web_ffi/ffi/types.dart
Executable file
326
spine-flutter/lib/web_ffi/ffi/types.dart
Executable file
@ -0,0 +1,326 @@
|
||||
import 'dart:typed_data';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import '../modules/module.dart';
|
||||
import '../modules/memory.dart';
|
||||
import '../modules/null_memory.dart';
|
||||
|
||||
import '../internal/type_utils.dart';
|
||||
|
||||
import '../web_ffi_meta.dart';
|
||||
|
||||
/// Represents a pointer into the native C memory corresponding to "NULL",
|
||||
/// e.g. a pointer with address 0.
|
||||
///
|
||||
/// You can compare any other pointer with this pointer using == to check
|
||||
/// if it's also an nullpointer.
|
||||
///
|
||||
/// Any other operation than comparing (e.g. calling [Pointer.cast])
|
||||
/// will result in exceptions.
|
||||
final Pointer<Never> nullptr = new Pointer<Never>._null();
|
||||
|
||||
/// Number of bytes used by native type T.
|
||||
///
|
||||
/// MUST NOT be called with types annoteted with @[unsized] or
|
||||
/// before [Memory.init()] was called or else an exception will be thrown.
|
||||
int sizeOf<T extends NativeType>() {
|
||||
int? size;
|
||||
if (isPointerType<T>()) {
|
||||
size = sizeMap[IntPtr];
|
||||
} else {
|
||||
size = sizeMap[T];
|
||||
}
|
||||
if (size != null) {
|
||||
return size;
|
||||
} else {
|
||||
throw new ArgumentError('The type $T is not known!');
|
||||
}
|
||||
}
|
||||
|
||||
bool _isUnsizedType<T extends NativeType>() {
|
||||
return isNativeFunctionType<T>() || isVoidType<T>();
|
||||
}
|
||||
|
||||
/// [NativeType]'s subtypes represent a native type in C.
|
||||
///
|
||||
/// [NativeType]'s subtypes (except [Pointer]) are not constructible
|
||||
/// in the Dart code and serve purely as markers in type signatures.
|
||||
@sealed
|
||||
@notConstructible
|
||||
class NativeType {}
|
||||
|
||||
/// Represents a native 64 bit double in C.
|
||||
///
|
||||
/// Double is not constructible in the Dart code and serves
|
||||
/// purely as marker in type signatures.
|
||||
@sealed
|
||||
@notConstructible
|
||||
class Double extends NativeType {}
|
||||
|
||||
/// Represents a native 32 bit float in C.
|
||||
///
|
||||
/// Float is not constructible in the Dart code and serves
|
||||
/// purely as marker in type signatures.
|
||||
@sealed
|
||||
@notConstructible
|
||||
class Float extends NativeType {}
|
||||
|
||||
/// Represents a native signed 8 bit integer in C.
|
||||
///
|
||||
/// Int8 is not constructible in the Dart code and serves
|
||||
/// purely as marker in type signatures.
|
||||
@sealed
|
||||
@notConstructible
|
||||
class Int8 extends NativeType {}
|
||||
|
||||
/// Represents a native signed 16 bit integer in C.
|
||||
///
|
||||
/// Int16 is not constructible in the Dart code and serves
|
||||
/// purely as marker in type signatures.
|
||||
@sealed
|
||||
@notConstructible
|
||||
class Int16 extends NativeType {}
|
||||
|
||||
/// Represents a native signed 32 bit integer in C.
|
||||
///
|
||||
/// Int32 is not constructible in the Dart code and serves
|
||||
/// purely as marker in type signatures.
|
||||
@sealed
|
||||
@notConstructible
|
||||
class Int32 extends NativeType {}
|
||||
|
||||
/// Represents a native signed 64 bit integer in C.
|
||||
///
|
||||
/// Int64 is not constructible in the Dart code and serves
|
||||
/// purely as marker in type signatures.
|
||||
@sealed
|
||||
@notConstructible
|
||||
class Int64 extends NativeType {}
|
||||
|
||||
/// Represents a native unsigned 8 bit integer in C.
|
||||
///
|
||||
/// Uint8 is not constructible in the Dart code and serves
|
||||
/// purely as marker in type signatures.
|
||||
@sealed
|
||||
@notConstructible
|
||||
class Uint8 extends NativeType {}
|
||||
|
||||
/// Represents a native unsigned 16 bit integer in C.
|
||||
///
|
||||
/// Uint16 is not constructible in the Dart code and serves
|
||||
/// purely as marker in type signatures.
|
||||
@sealed
|
||||
@notConstructible
|
||||
class Uint16 extends NativeType {}
|
||||
|
||||
/// Represents a native unsigned 32 bit integer in C.
|
||||
///
|
||||
/// Uint32 is not constructible in the Dart code and serves
|
||||
/// purely as marker in type signatures.
|
||||
@sealed
|
||||
@notConstructible
|
||||
class Uint32 extends NativeType {}
|
||||
|
||||
/// Represents a native unsigned 64 bit integer in C.
|
||||
///
|
||||
/// Uint64 is not constructible in the Dart code and serves
|
||||
/// purely as marker in type signatures.
|
||||
@sealed
|
||||
@notConstructible
|
||||
class Uint64 extends NativeType {}
|
||||
|
||||
/// Represents a native pointer-sized integer in C.
|
||||
///
|
||||
/// IntPtr is not constructible in the Dart code and serves
|
||||
/// purely as marker in type signatures.
|
||||
@sealed
|
||||
@notConstructible
|
||||
class IntPtr extends NativeType {}
|
||||
|
||||
/// Represents a function type in C.
|
||||
///
|
||||
/// NativeFunction is not constructible in the Dart code and serves
|
||||
/// purely as marker in type signatures.
|
||||
@sealed
|
||||
@notConstructible
|
||||
@unsized
|
||||
class NativeFunction<T extends Function> extends NativeType {}
|
||||
|
||||
/// Opaque's subtypes represent opaque types in C.
|
||||
///
|
||||
/// Classes that extend Opaque MUST NOT have a type argument!
|
||||
///
|
||||
/// Opaque's subtypes are not constructible in the Dart code and serve
|
||||
/// purely as markers in type signatures.
|
||||
@noGeneric
|
||||
@notConstructible
|
||||
class Opaque extends NativeType {}
|
||||
|
||||
/// Represents a void type in C.
|
||||
///
|
||||
/// Void is not constructible in the Dart code and serves
|
||||
/// purely as marker in type signatures.
|
||||
@sealed
|
||||
@notConstructible
|
||||
@unsized
|
||||
class Void extends NativeType {}
|
||||
|
||||
/// Represents a pointer into the native C memory. Cannot be extended.
|
||||
@sealed
|
||||
class Pointer<T extends NativeType> extends NativeType {
|
||||
//static Pointer<NativeFunction<T>> fromFunction<T extends Function>(Function f,
|
||||
// [Object? exceptionalReturn]) =>
|
||||
// throw new UnimplementedError();
|
||||
|
||||
/// Access to the raw pointer value.
|
||||
final int address;
|
||||
|
||||
/// The [Memory] object this pointer is bound to.
|
||||
///
|
||||
/// The `Memory` object backs this pointer, if the value of
|
||||
/// this pointer is accessed.
|
||||
@extra
|
||||
final Memory boundMemory;
|
||||
|
||||
/// How much memory in bytes the type this pointer points to occupies,
|
||||
/// or `null` for @[unsized] types.
|
||||
@extra
|
||||
final int? size;
|
||||
|
||||
factory Pointer._null() {
|
||||
return new Pointer._(0, new NullMemory(), null);
|
||||
}
|
||||
|
||||
/// Constructs a pointer from an address.
|
||||
///
|
||||
/// The optional parameter `bindTo` can be ommited, if and only if
|
||||
/// [Memory.global] is set, which is then used as `Memory` to bind to.
|
||||
factory Pointer.fromAddress(int ptr, [Memory? bindTo]) {
|
||||
bindTo = Memory.global;
|
||||
Memory m;
|
||||
if (bindTo != null) {
|
||||
m = bindTo;
|
||||
} else {
|
||||
throw new StateError(
|
||||
'No global memory set and no explcity memory to bind to given!');
|
||||
}
|
||||
return new Pointer._(ptr, m, _isUnsizedType<T>() ? null : sizeOf<T>());
|
||||
}
|
||||
|
||||
Pointer._(this.address, this.boundMemory, this.size);
|
||||
|
||||
/// Casts this pointer to an other type.
|
||||
Pointer<U> cast<U extends NativeType>() => new Pointer<U>._(
|
||||
address, boundMemory, _isUnsizedType<U>() ? null : sizeOf<U>());
|
||||
|
||||
/// Pointer arithmetic (takes element size into account).
|
||||
///
|
||||
/// Throws an [UnsupportedError] if called on a pointer with an @[unsized]
|
||||
/// type argument.
|
||||
Pointer<T> elementAt(int index) {
|
||||
int? s = size;
|
||||
if (s != null) {
|
||||
return new Pointer<T>._(address + index * s, boundMemory, s);
|
||||
} else {
|
||||
throw new UnsupportedError(
|
||||
'elementAt is not supported for unsized types!');
|
||||
}
|
||||
}
|
||||
|
||||
/// The hash code for a Pointer only depends on its address.
|
||||
@override
|
||||
int get hashCode => address;
|
||||
|
||||
/// Two pointers are equal if their address is the same, independently
|
||||
/// of their type argument and of the memory they are bound to.
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
(other is Pointer && other.address == address);
|
||||
|
||||
/// Returns a view of a single element at [index] (takes element
|
||||
/// size into account).
|
||||
///
|
||||
/// Any modifications to the data will also alter the [Memory] object.
|
||||
///
|
||||
/// Throws an [UnsupportedError] if called on a pointer with an @[unsized]
|
||||
/// type argument.
|
||||
@extra
|
||||
ByteData viewSingle(int index) {
|
||||
int? s = size;
|
||||
if (s != null) {
|
||||
return boundMemory.buffer.asByteData(address + index * s, s);
|
||||
} else {
|
||||
throw new UnsupportedError(
|
||||
'viewSingle is not supported for unsized types!');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a dynamically loaded C library.
|
||||
class DynamicLibrary {
|
||||
@extra
|
||||
final Memory boundMemory;
|
||||
|
||||
/// Creates a new instance based on the given module.
|
||||
///
|
||||
/// While for each [DynamicLibrary] a new [Memory] object is
|
||||
/// created, the [Memory] objects share the backing memory if
|
||||
/// they are created based on the same module.
|
||||
///
|
||||
/// The [registerMode] parameter can be used to control if the
|
||||
/// newly created [Memory] object should be registered as
|
||||
/// [Memory.global].
|
||||
@extra
|
||||
factory DynamicLibrary.fromModule(Module module,
|
||||
[MemoryRegisterMode registerMode =
|
||||
MemoryRegisterMode.onlyIfGlobalNotSet]) {
|
||||
Memory memory = createMemory(module);
|
||||
switch (registerMode) {
|
||||
case MemoryRegisterMode.yes:
|
||||
Memory.global = memory;
|
||||
break;
|
||||
case MemoryRegisterMode.no:
|
||||
break;
|
||||
case MemoryRegisterMode.onlyIfGlobalNotSet:
|
||||
if (Memory.global == null) {
|
||||
Memory.global = memory;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return new DynamicLibrary._(memory);
|
||||
}
|
||||
|
||||
DynamicLibrary._(this.boundMemory);
|
||||
|
||||
/// Looks up a symbol in the DynamicLibrary and returns its address in memory.
|
||||
///
|
||||
/// Throws an [ArgumentError] if it fails to lookup the symbol.
|
||||
///
|
||||
/// While this method checks if the underyling wasm symbol is a actually
|
||||
/// a function when you lookup a [NativeFunction]`<T>`, it does not check if
|
||||
/// the return type and parameters of `T` match the wasm function.
|
||||
Pointer<T> lookup<T extends NativeType>(String name) {
|
||||
WasmSymbol symbol = symbolByName(boundMemory, name);
|
||||
if (isNativeFunctionType<T>()) {
|
||||
if (symbol is FunctionDescription) {
|
||||
return new Pointer<T>.fromAddress(symbol.tableIndex, boundMemory);
|
||||
} else {
|
||||
throw new ArgumentError(
|
||||
'Tried to look up $name as a function, but it seems it is NOT a function!');
|
||||
}
|
||||
} else {
|
||||
return new Pointer<T>.fromAddress(symbol.address, boundMemory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Manages memory on the native heap.
|
||||
abstract class Allocator {
|
||||
/// Allocates byteCount bytes of memory on the native heap.
|
||||
///
|
||||
/// The parameter `alignment` is ignored.
|
||||
Pointer<T> allocate<T extends NativeType>(int byteCount, {int? alignment});
|
||||
|
||||
/// Releases memory allocated on the native heap.
|
||||
void free(Pointer<NativeType> pointer);
|
||||
}
|
||||
1091
spine-flutter/lib/web_ffi/internal/invoker_generated.dart
Executable file
1091
spine-flutter/lib/web_ffi/internal/invoker_generated.dart
Executable file
File diff suppressed because it is too large
Load Diff
271
spine-flutter/lib/web_ffi/internal/marshaller.dart
Executable file
271
spine-flutter/lib/web_ffi/internal/marshaller.dart
Executable file
@ -0,0 +1,271 @@
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import '../ffi/types.dart';
|
||||
import '../modules/exceptions.dart';
|
||||
import '../modules/memory.dart';
|
||||
import 'invoker_generated.dart';
|
||||
import 'type_utils.dart';
|
||||
|
||||
// Called from the invokers
|
||||
T execute<T>(Function base, List<Object> args, Memory memory) {
|
||||
if (T == DartVoidType) {
|
||||
Function.apply(base, args.map(_toJsType).toList());
|
||||
return null as T;
|
||||
} else {
|
||||
Object result = Function.apply(base, args.map(_toJsType).toList());
|
||||
return _toDartType<T>(result, memory);
|
||||
}
|
||||
}
|
||||
|
||||
DF marshall<NF extends Function, DF extends Function>(
|
||||
Function base, Memory memory) {
|
||||
return _inferFromSignature(DF.toString()).copyWith(base, memory).run as DF;
|
||||
}
|
||||
|
||||
Object _toJsType(Object dartObject) {
|
||||
if (dartObject is int || dartObject is double || dartObject is bool) {
|
||||
return dartObject;
|
||||
} else if (dartObject is Pointer) {
|
||||
return dartObject.address;
|
||||
} else {
|
||||
throw new MarshallingException(
|
||||
'Could not convert dart type ${dartObject.runtimeType} to a JavaScript type!');
|
||||
}
|
||||
}
|
||||
|
||||
InvokeHelper _inferFromSignature(String signature) {
|
||||
String returnType = signature.split('=>').last.trim();
|
||||
if (returnType.startsWith(pointerPointerPointerPrefix)) {
|
||||
throw new MarshallingException(
|
||||
'Nesting pointers is only supported to a deepth of 2!' +
|
||||
'\nThis means that you can write Pointer<Pointer<X>> but not Pointer<Pointer<Pointer<X>>>, ...');
|
||||
}
|
||||
InvokeHelper? h = _knownTypes[returnType];
|
||||
if (h != null) {
|
||||
return h;
|
||||
} else {
|
||||
if (returnType.startsWith(pointerNativeFunctionPrefix)) {
|
||||
throw new MarshallingException(
|
||||
'Using pointers to native functions as return type is only allowed if the type of the native function is dynamic!' +
|
||||
'\nThis means that only Pointer<NativeFunction<dynamic>> is allowed!');
|
||||
} else {
|
||||
throw new MarshallingException(
|
||||
'Unknown type $returnType (infered from $signature), all marshallable types: ${listKnownTypes()}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
List<String> listKnownTypes() =>
|
||||
new List<String>.of(_knownTypes.keys, growable: false);
|
||||
|
||||
final Map<String, InvokeHelper> _knownTypes = {
|
||||
typeString<int>(): new InvokeHelper<int>(null, null),
|
||||
typeString<double>(): new InvokeHelper<double>(null, null),
|
||||
typeString<bool>(): new InvokeHelper<bool>(null, null),
|
||||
typeString<void>(): new InvokeHelper<void>(null, null)
|
||||
};
|
||||
|
||||
void registerNativeMarshallerType<T extends NativeType>() {
|
||||
_knownTypes[typeString<Pointer<T>>()] =
|
||||
new InvokeHelper<Pointer<T>>(null, null);
|
||||
_knownTypes[typeString<Pointer<Pointer<T>>>()] =
|
||||
new InvokeHelper<Pointer<Pointer<T>>>(null, null);
|
||||
}
|
||||
|
||||
void registerNativeMarshallerOpaque<T extends Opaque>() {
|
||||
_knownTypes[typeString<Pointer<T>>()] = new OpaqueInvokeHelper<T>(null, null);
|
||||
_knownTypes[typeString<Pointer<Pointer<T>>>()] =
|
||||
new OpaqueInvokeHelperSquare<T>(null, null);
|
||||
}
|
||||
|
||||
T _toDartType<T>(Object o, Memory bind) {
|
||||
if (T == int) {
|
||||
if (o is int) {
|
||||
return o as T;
|
||||
} else {
|
||||
throw new MarshallingException.typeMissmatch(T, o);
|
||||
}
|
||||
} else if (T == double) {
|
||||
if (o is double) {
|
||||
return o as T;
|
||||
} else {
|
||||
throw new MarshallingException.typeMissmatch(T, o);
|
||||
}
|
||||
} else if (T == bool) {
|
||||
if (o is bool) {
|
||||
return o as T;
|
||||
} else {
|
||||
throw new MarshallingException.typeMissmatch(T, o);
|
||||
}
|
||||
} else {
|
||||
if (T == Pointer_Void) {
|
||||
if (o is int) {
|
||||
return new Pointer<Void>.fromAddress(o, bind) as T;
|
||||
} else {
|
||||
throw new MarshallingException.noAddress(o);
|
||||
}
|
||||
} else if (T == Pointer_IntPtr) {
|
||||
if (o is int) {
|
||||
return new Pointer<IntPtr>.fromAddress(o, bind) as T;
|
||||
} else {
|
||||
throw new MarshallingException.noAddress(o);
|
||||
}
|
||||
} else if (T == Pointer_Int8) {
|
||||
if (o is int) {
|
||||
return new Pointer<Int8>.fromAddress(o, bind) as T;
|
||||
} else {
|
||||
throw new MarshallingException.noAddress(o);
|
||||
}
|
||||
} else if (T == Pointer_Int16) {
|
||||
if (o is int) {
|
||||
return new Pointer<Int16>.fromAddress(o, bind) as T;
|
||||
} else {
|
||||
throw new MarshallingException.noAddress(o);
|
||||
}
|
||||
} else if (T == Pointer_Int32) {
|
||||
if (o is int) {
|
||||
return new Pointer<Int32>.fromAddress(o, bind) as T;
|
||||
} else {
|
||||
throw new MarshallingException.noAddress(o);
|
||||
}
|
||||
} else if (T == Pointer_Int64) {
|
||||
if (o is int) {
|
||||
return new Pointer<Int64>.fromAddress(o, bind) as T;
|
||||
} else {
|
||||
throw new MarshallingException.noAddress(o);
|
||||
}
|
||||
} else if (T == Pointer_Double) {
|
||||
if (o is int) {
|
||||
return new Pointer<Double>.fromAddress(o, bind) as T;
|
||||
} else {
|
||||
throw new MarshallingException.noAddress(o);
|
||||
}
|
||||
} else if (T == Pointer_Uint8) {
|
||||
if (o is int) {
|
||||
return new Pointer<Uint8>.fromAddress(o, bind) as T;
|
||||
} else {
|
||||
throw new MarshallingException.noAddress(o);
|
||||
}
|
||||
} else if (T == Pointer_Uint16) {
|
||||
if (o is int) {
|
||||
return new Pointer<Uint16>.fromAddress(o, bind) as T;
|
||||
} else {
|
||||
throw new MarshallingException.noAddress(o);
|
||||
}
|
||||
} else if (T == Pointer_Uint32) {
|
||||
if (o is int) {
|
||||
return new Pointer<Uint32>.fromAddress(o, bind) as T;
|
||||
} else {
|
||||
throw new MarshallingException.noAddress(o);
|
||||
}
|
||||
} else if (T == Pointer_Uint64) {
|
||||
if (o is int) {
|
||||
return new Pointer<Uint64>.fromAddress(o, bind) as T;
|
||||
} else {
|
||||
throw new MarshallingException.noAddress(o);
|
||||
}
|
||||
} else if (T == Pointer_Float) {
|
||||
if (o is int) {
|
||||
return new Pointer<Float>.fromAddress(o, bind) as T;
|
||||
} else {
|
||||
throw new MarshallingException.noAddress(o);
|
||||
}
|
||||
} else if (T == Pointer_Opaque) {
|
||||
if (o is int) {
|
||||
return new Pointer<Opaque>.fromAddress(o, bind) as T;
|
||||
} else {
|
||||
throw new MarshallingException.noAddress(o);
|
||||
}
|
||||
} else if (T == Pointer_NativeFunction_dynamic) {
|
||||
if (o is int) {
|
||||
return new Pointer<NativeFunction<dynamic>>.fromAddress(o, bind) as T;
|
||||
} else {
|
||||
throw new MarshallingException.noAddress(o);
|
||||
}
|
||||
} else {
|
||||
if (T == Pointer_Pointer_Void) {
|
||||
if (o is int) {
|
||||
return new Pointer<Pointer<Void>>.fromAddress(o, bind) as T;
|
||||
} else {
|
||||
throw new MarshallingException.noAddress(o);
|
||||
}
|
||||
} else if (T == Pointer_Pointer_IntPtr) {
|
||||
if (o is int) {
|
||||
return new Pointer<Pointer<IntPtr>>.fromAddress(o, bind) as T;
|
||||
} else {
|
||||
throw new MarshallingException.noAddress(o);
|
||||
}
|
||||
} else if (T == Pointer_Pointer_Int8) {
|
||||
if (o is int) {
|
||||
return new Pointer<Pointer<Int8>>.fromAddress(o, bind) as T;
|
||||
} else {
|
||||
throw new MarshallingException.noAddress(o);
|
||||
}
|
||||
} else if (T == Pointer_Pointer_Int16) {
|
||||
if (o is int) {
|
||||
return new Pointer<Pointer<Int16>>.fromAddress(o, bind) as T;
|
||||
} else {
|
||||
throw new MarshallingException.noAddress(o);
|
||||
}
|
||||
} else if (T == Pointer_Pointer_Int32) {
|
||||
if (o is int) {
|
||||
return new Pointer<Pointer<Int32>>.fromAddress(o, bind) as T;
|
||||
} else {
|
||||
throw new MarshallingException.noAddress(o);
|
||||
}
|
||||
} else if (T == Pointer_Pointer_Int64) {
|
||||
if (o is int) {
|
||||
return new Pointer<Pointer<Int64>>.fromAddress(o, bind) as T;
|
||||
} else {
|
||||
throw new MarshallingException.noAddress(o);
|
||||
}
|
||||
} else if (T == Pointer_Pointer_Double) {
|
||||
if (o is int) {
|
||||
return new Pointer<Pointer<Double>>.fromAddress(o, bind) as T;
|
||||
} else {
|
||||
throw new MarshallingException.noAddress(o);
|
||||
}
|
||||
} else if (T == Pointer_Pointer_Uint8) {
|
||||
if (o is int) {
|
||||
return new Pointer<Pointer<Uint8>>.fromAddress(o, bind) as T;
|
||||
} else {
|
||||
throw new MarshallingException.noAddress(o);
|
||||
}
|
||||
} else if (T == Pointer_Pointer_Uint16) {
|
||||
if (o is int) {
|
||||
return new Pointer<Pointer<Uint16>>.fromAddress(o, bind) as T;
|
||||
} else {
|
||||
throw new MarshallingException.noAddress(o);
|
||||
}
|
||||
} else if (T == Pointer_Pointer_Uint32) {
|
||||
if (o is int) {
|
||||
return new Pointer<Pointer<Uint32>>.fromAddress(o, bind) as T;
|
||||
} else {
|
||||
throw new MarshallingException.noAddress(o);
|
||||
}
|
||||
} else if (T == Pointer_Pointer_Uint64) {
|
||||
if (o is int) {
|
||||
return new Pointer<Pointer<Uint64>>.fromAddress(o, bind) as T;
|
||||
} else {
|
||||
throw new MarshallingException.noAddress(o);
|
||||
}
|
||||
} else if (T == Pointer_Pointer_Float) {
|
||||
if (o is int) {
|
||||
return new Pointer<Pointer<Float>>.fromAddress(o, bind) as T;
|
||||
} else {
|
||||
throw new MarshallingException.noAddress(o);
|
||||
}
|
||||
} else if (T == Pointer_Pointer_Opaque) {
|
||||
if (o is int) {
|
||||
return new Pointer<Pointer<Opaque>>.fromAddress(o, bind) as T;
|
||||
} else {
|
||||
throw new MarshallingException.noAddress(o);
|
||||
}
|
||||
} else {
|
||||
throw new MarshallingException(
|
||||
'Can not back-marshall to type $T (object type is ${o.runtimeType}');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
62
spine-flutter/lib/web_ffi/internal/type_utils.dart
Executable file
62
spine-flutter/lib/web_ffi/internal/type_utils.dart
Executable file
@ -0,0 +1,62 @@
|
||||
import '../ffi/types.dart';
|
||||
|
||||
/// Hacky workadround, see https://github.com/dart-lang/language/issues/123
|
||||
Type _extractType<T>() => T;
|
||||
String typeString<T>() => _extractType<T>().toString();
|
||||
|
||||
// Variable names begin with a capital letter on purpose (opposing dart conventions) to hilight that
|
||||
// they are treated like types (which are written with a captial letter in dart).
|
||||
final Type Pointer_IntPtr = _extractType<Pointer<IntPtr>>();
|
||||
final Type Pointer_Void = _extractType<Pointer<Void>>();
|
||||
final Type Pointer_Int8 = _extractType<Pointer<Int8>>();
|
||||
final Type Pointer_Int16 = _extractType<Pointer<Int16>>();
|
||||
final Type Pointer_Int32 = _extractType<Pointer<Int32>>();
|
||||
final Type Pointer_Int64 = _extractType<Pointer<Int64>>();
|
||||
final Type Pointer_Double = _extractType<Pointer<Double>>();
|
||||
final Type Pointer_Uint8 = _extractType<Pointer<Uint8>>();
|
||||
final Type Pointer_Uint16 = _extractType<Pointer<Uint16>>();
|
||||
final Type Pointer_Uint32 = _extractType<Pointer<Uint32>>();
|
||||
final Type Pointer_Uint64 = _extractType<Pointer<Uint64>>();
|
||||
final Type Pointer_Float = _extractType<Pointer<Float>>();
|
||||
final Type Pointer_Opaque = _extractType<Pointer<Opaque>>();
|
||||
final Type Pointer_Pointer_IntPtr = _extractType<Pointer<Pointer<IntPtr>>>();
|
||||
final Type Pointer_Pointer_Void = _extractType<Pointer<Pointer<Void>>>();
|
||||
final Type Pointer_Pointer_Int8 = _extractType<Pointer<Pointer<Int8>>>();
|
||||
final Type Pointer_Pointer_Int16 = _extractType<Pointer<Pointer<Int16>>>();
|
||||
final Type Pointer_Pointer_Int32 = _extractType<Pointer<Pointer<Int32>>>();
|
||||
final Type Pointer_Pointer_Int64 = _extractType<Pointer<Pointer<Int64>>>();
|
||||
final Type Pointer_Pointer_Double = _extractType<Pointer<Pointer<Double>>>();
|
||||
final Type Pointer_Pointer_Uint8 = _extractType<Pointer<Pointer<Uint8>>>();
|
||||
final Type Pointer_Pointer_Uint16 = _extractType<Pointer<Pointer<Uint16>>>();
|
||||
final Type Pointer_Pointer_Uint32 = _extractType<Pointer<Pointer<Uint32>>>();
|
||||
final Type Pointer_Pointer_Uint64 = _extractType<Pointer<Pointer<Uint64>>>();
|
||||
final Type Pointer_Pointer_Float = _extractType<Pointer<Pointer<Float>>>();
|
||||
final Type Pointer_Pointer_Opaque = _extractType<Pointer<Pointer<Opaque>>>();
|
||||
final Type Pointer_NativeFunction_dynamic =
|
||||
_extractType<Pointer<NativeFunction<dynamic>>>();
|
||||
final Type DartVoidType = _extractType<void>();
|
||||
final Type FfiVoidType = _extractType<Void>();
|
||||
|
||||
final String _dynamicTypeString = typeString<dynamic>();
|
||||
|
||||
final String pointerPointerPointerPrefix =
|
||||
typeString<Pointer<Pointer<Pointer<dynamic>>>>()
|
||||
.split(_dynamicTypeString)
|
||||
.first;
|
||||
|
||||
final String pointerNativeFunctionPrefix =
|
||||
typeString<Pointer<NativeFunction<dynamic>>>()
|
||||
.split(_dynamicTypeString)
|
||||
.first;
|
||||
|
||||
final String _nativeFunctionPrefix =
|
||||
typeString<NativeFunction<dynamic>>().split(_dynamicTypeString).first;
|
||||
bool isNativeFunctionType<T extends NativeType>() =>
|
||||
typeString<T>().startsWith(_nativeFunctionPrefix);
|
||||
|
||||
final String _pointerPrefix =
|
||||
typeString<Pointer<dynamic>>().split(_dynamicTypeString).first;
|
||||
bool isPointerType<T extends NativeType>() =>
|
||||
typeString<T>().startsWith(_pointerPrefix);
|
||||
|
||||
bool isVoidType<T extends NativeType>() => _extractType<T>() == FfiVoidType;
|
||||
35
spine-flutter/lib/web_ffi/meta/meta.dart
Executable file
35
spine-flutter/lib/web_ffi/meta/meta.dart
Executable file
@ -0,0 +1,35 @@
|
||||
class _Extra {
|
||||
const _Extra();
|
||||
}
|
||||
|
||||
/// A class, field or method annotated with extra is present in `web_ffi`,
|
||||
/// but not in `dart:ffi`.
|
||||
const _Extra extra = const _Extra();
|
||||
|
||||
class _NoGeneric {
|
||||
const _NoGeneric();
|
||||
}
|
||||
|
||||
/// If a class which is annotead with [noGeneric] is extended or implemented,
|
||||
/// the derived class MUST NOT impose a type argument!
|
||||
const _NoGeneric noGeneric = const _NoGeneric();
|
||||
|
||||
class _NotConstructible {
|
||||
const _NotConstructible();
|
||||
}
|
||||
|
||||
/// A [NativeType] annotated with unsized should not be instantiated.
|
||||
///
|
||||
/// However, they are not marked as `abstract` to meet the dart:ffi API.
|
||||
const _NotConstructible notConstructible = const _NotConstructible();
|
||||
|
||||
class _Unsized {
|
||||
const _Unsized();
|
||||
}
|
||||
|
||||
/// A [NativeType] annotated with unsized does not have a predefined size.
|
||||
///
|
||||
/// Unsized [NativeType]s do not support [sizeOf] because their size is unknown,
|
||||
/// so calling [sizeOf] with an @[unsized] [NativeType] will throw an exception.
|
||||
/// Consequently, [Pointer.elementAt] is not available and will also throw an exception.
|
||||
const _Unsized unsized = const _Unsized();
|
||||
192
spine-flutter/lib/web_ffi/modules/emscripten/emscripten_module.dart
Executable file
192
spine-flutter/lib/web_ffi/modules/emscripten/emscripten_module.dart
Executable file
@ -0,0 +1,192 @@
|
||||
@JS()
|
||||
library emscripten_module;
|
||||
|
||||
import 'dart:typed_data';
|
||||
import 'package:js/js.dart';
|
||||
import 'package:js/js_util.dart';
|
||||
import '../module.dart';
|
||||
import '../../web_ffi_meta.dart';
|
||||
|
||||
@JS('globalThis')
|
||||
external Object get _globalThis;
|
||||
|
||||
@JS('Object.entries')
|
||||
external List? _entries(Object? o);
|
||||
|
||||
@JS()
|
||||
@anonymous
|
||||
class _EmscriptenModuleJs {
|
||||
external Uint8List? get wasmBinary;
|
||||
external Uint8List? get HEAPU8;
|
||||
external Object? get asm;
|
||||
|
||||
// Must have an unnamed factory constructor with named arguments.
|
||||
external factory _EmscriptenModuleJs({Uint8List wasmBinary});
|
||||
}
|
||||
|
||||
const String _github = r'https://github.com/EPNW/web_ffi';
|
||||
String _adu(WasmSymbol? original, WasmSymbol? tried) =>
|
||||
'CRITICAL EXCEPTION! Address double use! This should never happen, please report this issue on github immediately at $_github' +
|
||||
'\r\nOriginal: $original' +
|
||||
'\r\nTried: $tried';
|
||||
|
||||
typedef int _Malloc(int size);
|
||||
typedef void _Free(int address);
|
||||
|
||||
FunctionDescription _fromWasmFunction(String name, Function func) {
|
||||
String? s = getProperty(func, 'name');
|
||||
if (s != null) {
|
||||
int? index = int.tryParse(s);
|
||||
if (index != null) {
|
||||
int? length = getProperty(func, 'length');
|
||||
if (length != null) {
|
||||
return new FunctionDescription(
|
||||
tableIndex: index,
|
||||
name: name,
|
||||
function: func,
|
||||
argumentCount: length);
|
||||
} else {
|
||||
throw new ArgumentError('$name does not seem to be a function symbol!');
|
||||
}
|
||||
} else {
|
||||
throw new ArgumentError('$name does not seem to be a function symbol!');
|
||||
}
|
||||
} else {
|
||||
throw new ArgumentError('$name does not seem to be a function symbol!');
|
||||
}
|
||||
}
|
||||
|
||||
/// Documentation is in `emscripten_module_stub.dart`!
|
||||
@extra
|
||||
class EmscriptenModule extends Module {
|
||||
static Function _moduleFunction(String moduleName) {
|
||||
Function? moduleFunction = getProperty(_globalThis, moduleName);
|
||||
if (moduleFunction != null) {
|
||||
return moduleFunction;
|
||||
} else {
|
||||
throw StateError('Could not find a emscripten module named $moduleName');
|
||||
}
|
||||
}
|
||||
|
||||
/// Documentation is in `emscripten_module_stub.dart`!
|
||||
static Future<EmscriptenModule> process(String moduleName) async {
|
||||
Function moduleFunction = _moduleFunction(moduleName);
|
||||
_EmscriptenModuleJs module = new _EmscriptenModuleJs();
|
||||
Object? o = moduleFunction(module);
|
||||
if (o != null) {
|
||||
await promiseToFuture(o);
|
||||
return new EmscriptenModule._fromJs(module);
|
||||
} else {
|
||||
throw new StateError('Could not instantiate an emscripten module!');
|
||||
}
|
||||
}
|
||||
|
||||
/// Documentation is in `emscripten_module_stub.dart`!
|
||||
static Future<EmscriptenModule> compile(
|
||||
Uint8List wasmBinary, String moduleName) async {
|
||||
Function moduleFunction = _moduleFunction(moduleName);
|
||||
_EmscriptenModuleJs module =
|
||||
new _EmscriptenModuleJs(wasmBinary: wasmBinary);
|
||||
Object? o = moduleFunction(module);
|
||||
if (o != null) {
|
||||
await promiseToFuture(o);
|
||||
return new EmscriptenModule._fromJs(module);
|
||||
} else {
|
||||
throw new StateError('Could not instantiate an emscripten module!');
|
||||
}
|
||||
}
|
||||
|
||||
final _EmscriptenModuleJs _emscriptenModuleJs;
|
||||
final List<WasmSymbol> _exports;
|
||||
final _Malloc _malloc;
|
||||
final _Free _free;
|
||||
|
||||
@override
|
||||
List<WasmSymbol> get exports => _exports;
|
||||
|
||||
EmscriptenModule._(
|
||||
this._emscriptenModuleJs, this._exports, this._malloc, this._free);
|
||||
|
||||
factory EmscriptenModule._fromJs(_EmscriptenModuleJs module) {
|
||||
Object? asm = module.asm;
|
||||
if (asm != null) {
|
||||
Map<int, WasmSymbol> knownAddresses = {};
|
||||
_Malloc? malloc;
|
||||
_Free? free;
|
||||
List<WasmSymbol> exports = [];
|
||||
List? entries = _entries(asm);
|
||||
if (entries != null) {
|
||||
for (dynamic entry in entries) {
|
||||
if (entry is List) {
|
||||
Object value = entry.last;
|
||||
if (value is int) {
|
||||
Global g =
|
||||
new Global(address: value, name: entry.first as String);
|
||||
if (knownAddresses.containsKey(value) &&
|
||||
knownAddresses[value] is! Global) {
|
||||
throw new StateError(_adu(knownAddresses[value], g));
|
||||
}
|
||||
knownAddresses[value] = g;
|
||||
exports.add(g);
|
||||
} else if (value is Function) {
|
||||
FunctionDescription description =
|
||||
_fromWasmFunction(entry.first as String, value);
|
||||
// It might happen that there are two different c functions that do nothing else than calling the same underlying c function
|
||||
// In this case, a compiler might substitute both functions with the underlying c function
|
||||
// So we got two functions with different names at the same table index
|
||||
// So it is actually ok if there are two things at the same address, as long as they are both functions
|
||||
if (knownAddresses.containsKey(description.tableIndex) &&
|
||||
knownAddresses[description.tableIndex]
|
||||
is! FunctionDescription) {
|
||||
throw new StateError(
|
||||
_adu(knownAddresses[description.tableIndex], description));
|
||||
}
|
||||
knownAddresses[description.tableIndex] = description;
|
||||
exports.add(description);
|
||||
if (description.name == 'malloc') {
|
||||
malloc = description.function as _Malloc;
|
||||
} else if (description.name == 'free') {
|
||||
free = description.function as _Free;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new StateError(
|
||||
'Unexpected entry in entries(Module[\'asm\'])!');
|
||||
}
|
||||
}
|
||||
if (malloc != null) {
|
||||
if (free != null) {
|
||||
return new EmscriptenModule._(module, exports, malloc, free);
|
||||
} else {
|
||||
throw new StateError('Module does not export the free function!');
|
||||
}
|
||||
} else {
|
||||
throw new StateError('Module does not export the malloc function!');
|
||||
}
|
||||
} else {
|
||||
throw new StateError(
|
||||
'JavaScript error: Could not access entries of Module[\'asm\']!');
|
||||
}
|
||||
} else {
|
||||
throw new StateError(
|
||||
'Could not access Module[\'asm\'], are your sure your module was compiled using emscripten?');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void free(int pointer) => _free(pointer);
|
||||
|
||||
@override
|
||||
ByteBuffer get heap => _getHeap();
|
||||
ByteBuffer _getHeap() {
|
||||
Uint8List? h = _emscriptenModuleJs.HEAPU8;
|
||||
if (h != null) {
|
||||
return h.buffer;
|
||||
} else {
|
||||
throw StateError('Unexpected memory error!');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int malloc(int size) => _malloc(size);
|
||||
}
|
||||
71
spine-flutter/lib/web_ffi/modules/emscripten/emscripten_module_stub.dart
Executable file
71
spine-flutter/lib/web_ffi/modules/emscripten/emscripten_module_stub.dart
Executable file
@ -0,0 +1,71 @@
|
||||
import 'dart:typed_data';
|
||||
import '../module.dart';
|
||||
|
||||
import '../../web_ffi_meta.dart';
|
||||
|
||||
/// Provides access to WebAssembly compiled with [emscripten](https://emscripten.org).
|
||||
///
|
||||
/// WebAssembly compiled with emscripten comes with an `<moduleName>.wasm`
|
||||
/// and an additional `<moduleName>.js` glue JavaScript file. The later is
|
||||
/// required to be loaded on the page before calling any of this classes
|
||||
/// functions.
|
||||
///
|
||||
/// The WebAssembly must have been compiled with the `-s MODULARIZE=1`
|
||||
/// and `-s EXPORT_NAME=<moduleName>` flags. Futhermore the `<moduleName.js>`
|
||||
/// must contain all exported WebAssembly functions that should be usable from
|
||||
/// dart, so using `-s MAIN_MODULE=1` might be advisable.
|
||||
///
|
||||
/// For a detailed walkthrough on how to create and inject these files,
|
||||
/// see the [example](https://github.com/EPNW/web_ffi/blob/master/example/README.md).
|
||||
///
|
||||
/// On platforms where [dart:js](https://api.dart.dev/stable/dart-js/dart-js-library.html)
|
||||
/// is not available, all methods throw [UnsupportedError]s.
|
||||
@extra
|
||||
class EmscriptenModule extends Module {
|
||||
/// Connects to the JavaScript glue of the emscripten module.
|
||||
///
|
||||
/// This happens in the following way:
|
||||
/// First, a JavaScript property named `moduleName` of the global object
|
||||
/// is accessed, which should contain a function. Then this function is
|
||||
/// called and expected to return a JavaScript emscripten module.
|
||||
///
|
||||
/// The JavaScript emscripten module is responsible for retriving the
|
||||
/// WebAssembly and compile it accordingly.
|
||||
///
|
||||
/// On platforms where [dart:js](https://api.dart.dev/stable/dart-js/dart-js-library.html)
|
||||
/// is not available, an [UnsupportedError] is thrown.
|
||||
static Future<EmscriptenModule> process(String moduleName) =>
|
||||
throw new UnsupportedError(
|
||||
'Emscripten operations are only allowed on the web (where dart:js is present)!');
|
||||
|
||||
/// Connects to the JavaScript glue of the emscripten module.
|
||||
///
|
||||
/// Works like [process], except that the bytes of the WebAssembly
|
||||
/// are passed to the JavaScript emscripten module, so it is
|
||||
/// your responsibility to fetch it.
|
||||
///
|
||||
/// On platforms where [dart:js](https://api.dart.dev/stable/dart-js/dart-js-library.html)
|
||||
/// is not available, an [UnsupportedError] is thrown.
|
||||
static Future<EmscriptenModule> compile(
|
||||
Uint8List wasmBinary, String moduleName) =>
|
||||
throw new UnsupportedError(
|
||||
'Emscripten operations are only allowed on the web (where dart:js is present)!');
|
||||
|
||||
EmscriptenModule._();
|
||||
|
||||
@override
|
||||
List<WasmSymbol> get exports => throw new UnsupportedError(
|
||||
'Emscripten operations are only allowed on the web (where dart:js is present)!');
|
||||
|
||||
@override
|
||||
void free(int pointer) => throw new UnsupportedError(
|
||||
'Emscripten operations are only allowed on the web (where dart:js is present)!');
|
||||
|
||||
@override
|
||||
ByteBuffer get heap => throw new UnsupportedError(
|
||||
'Emscripten operations are only allowed on the web (where dart:js is present)!');
|
||||
|
||||
@override
|
||||
int malloc(int size) => throw new UnsupportedError(
|
||||
'Emscripten operations are only allowed on the web (where dart:js is present)!');
|
||||
}
|
||||
18
spine-flutter/lib/web_ffi/modules/exceptions.dart
Executable file
18
spine-flutter/lib/web_ffi/modules/exceptions.dart
Executable file
@ -0,0 +1,18 @@
|
||||
/// Occures if it's not possible to convert dart types to JavaScript types.
|
||||
///
|
||||
/// This usually happens if a not allowed type is uses as a [NativeType]'s
|
||||
/// type argument, or a not allowed return value of a [NativeFunction] is
|
||||
/// used.
|
||||
class MarshallingException implements Exception {
|
||||
final dynamic message;
|
||||
const MarshallingException([this.message]);
|
||||
|
||||
MarshallingException.noAddress(Object o)
|
||||
: this('Expected a address (int) but found ${o.runtimeType}');
|
||||
|
||||
MarshallingException.typeMissmatch(Type t, Object o)
|
||||
: this('Expected a type of $t but object has type ${o.runtimeType}');
|
||||
|
||||
@override
|
||||
String toString() => new Exception(message).toString();
|
||||
}
|
||||
116
spine-flutter/lib/web_ffi/modules/memory.dart
Executable file
116
spine-flutter/lib/web_ffi/modules/memory.dart
Executable file
@ -0,0 +1,116 @@
|
||||
import 'dart:typed_data';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import 'module.dart';
|
||||
import '../ffi/types.dart';
|
||||
import '../internal/marshaller.dart';
|
||||
import '../web_ffi_meta.dart';
|
||||
|
||||
final Map<Type, int> sizeMap = {};
|
||||
|
||||
/// Must be called with each type that extends Opaque before
|
||||
/// attemtping to use that type.
|
||||
@extra
|
||||
void registerOpaqueType<T extends Opaque>() {
|
||||
sizeMap[T] = sizeOf<Opaque>();
|
||||
registerNativeMarshallerOpaque<T>();
|
||||
}
|
||||
|
||||
void _registerType<T extends NativeType>(int size) {
|
||||
sizeMap[T] = size;
|
||||
registerNativeMarshallerType<T>();
|
||||
}
|
||||
|
||||
/// Represents the native heap.
|
||||
@extra
|
||||
class Memory implements Allocator {
|
||||
/// The endianess of data stored.
|
||||
///
|
||||
/// The WebAssembly speficiation defines little endianess, so this is a constant.
|
||||
static const Endian endianess = Endian.little;
|
||||
|
||||
/// Must be called before working with `web_ffi` to initalize all type sizes.
|
||||
///
|
||||
/// The optional parameter [pointerSizeBytes] can be used to adjust the size
|
||||
/// of pointers. It defaults to `4` since WebAssembly usually uses 32 bit pointers.
|
||||
/// If you want to use wasm64, set [pointerSizeBytes] to `8` to denote 64 bit pointers.
|
||||
static void init([int pointerSizeBytes = 4]) {
|
||||
_registerType<Float>(4);
|
||||
_registerType<Double>(8);
|
||||
_registerType<Int8>(1);
|
||||
_registerType<Uint8>(1);
|
||||
_registerType<Int16>(2);
|
||||
_registerType<Uint16>(2);
|
||||
_registerType<Int32>(4);
|
||||
_registerType<Uint32>(4);
|
||||
_registerType<Int64>(8);
|
||||
_registerType<Uint64>(8);
|
||||
_registerType<IntPtr>(pointerSizeBytes);
|
||||
_registerType<Opaque>(pointerSizeBytes);
|
||||
registerNativeMarshallerType<Void>();
|
||||
registerNativeMarshallerType<NativeFunction<dynamic>>();
|
||||
}
|
||||
|
||||
/// The default [Memory] object to use.
|
||||
///
|
||||
/// This field is null until it is either manually set to a [Memory] object,
|
||||
/// or automatically set by [DynamicLibrary.fromModule].
|
||||
///
|
||||
/// This is most notably used when creating a pointer using [Pointer.fromAddress]
|
||||
/// with no explicite memory to bind to given.
|
||||
static Memory? global;
|
||||
|
||||
/// Can be used to directly access the memory of this object.
|
||||
///
|
||||
/// The value of this field should not be stored in a state variable,
|
||||
/// since the returned buffer may change over time.
|
||||
@doNotStore
|
||||
ByteBuffer get buffer => _module.heap;
|
||||
|
||||
final Module _module;
|
||||
final Map<String, WasmSymbol> _symbolsByName;
|
||||
final Map<int, WasmSymbol> _symbolsByAddress;
|
||||
|
||||
Memory._(this._module)
|
||||
: _symbolsByAddress = new Map<int, WasmSymbol>.fromEntries(_module.exports
|
||||
.map<MapEntry<int, WasmSymbol>>((WasmSymbol symbol) =>
|
||||
new MapEntry<int, WasmSymbol>(symbol.address, symbol))),
|
||||
_symbolsByName = new Map<String, WasmSymbol>.fromEntries(_module.exports
|
||||
.map<MapEntry<String, WasmSymbol>>((WasmSymbol symbol) =>
|
||||
new MapEntry<String, WasmSymbol>(symbol.name, symbol)));
|
||||
|
||||
@override
|
||||
Pointer<T> allocate<T extends NativeType>(int byteCount, {int? alignment}) {
|
||||
return new Pointer<T>.fromAddress(_module.malloc(byteCount), this);
|
||||
}
|
||||
|
||||
@override
|
||||
void free(Pointer<NativeType> pointer) {
|
||||
_module.free(pointer.address);
|
||||
}
|
||||
}
|
||||
|
||||
Memory createMemory(Module module) => new Memory._(module);
|
||||
|
||||
WasmSymbol symbolByAddress(Memory m, int address) {
|
||||
WasmSymbol? s = m._symbolsByAddress[address];
|
||||
if (s != null) {
|
||||
return s;
|
||||
} else {
|
||||
throw new ArgumentError('Could not find symbol at $address!');
|
||||
}
|
||||
}
|
||||
|
||||
WasmSymbol symbolByName(Memory m, String name) {
|
||||
WasmSymbol? s = m._symbolsByName[name];
|
||||
if (s != null) {
|
||||
return s;
|
||||
} else {
|
||||
throw new ArgumentError('Could not find symbol $name!');
|
||||
}
|
||||
}
|
||||
|
||||
/// Used on [DynamicLibrary] creation to control if the therby newly created
|
||||
/// [Memory] object should be registered as [Memory.global].
|
||||
@extra
|
||||
enum MemoryRegisterMode { yes, no, onlyIfGlobalNotSet }
|
||||
114
spine-flutter/lib/web_ffi/modules/module.dart
Executable file
114
spine-flutter/lib/web_ffi/modules/module.dart
Executable file
@ -0,0 +1,114 @@
|
||||
import 'dart:typed_data';
|
||||
import 'package:meta/meta.dart';
|
||||
import '../web_ffi_meta.dart';
|
||||
|
||||
/// Base class to interact with the WebAssembly.
|
||||
///
|
||||
/// Currently, only [emscripten](https://emscripten.org) compiled WebAssembly is supported,
|
||||
/// so the only concrete implementation if this class is [EmscriptenModule].
|
||||
///
|
||||
/// To support additional mechanisms/frameworks/compilers, create a subclass of
|
||||
/// [Module].
|
||||
@extra
|
||||
abstract class Module {
|
||||
/// Provides access to the malloc function in WebAssembly.
|
||||
///
|
||||
/// Allocates `size` bytes of memory and returns the corresponding
|
||||
/// address.
|
||||
///
|
||||
/// Memory allocated by this should be [free]d afterwards.
|
||||
int malloc(int size);
|
||||
|
||||
/// Provides access to the free function in WebAssembly.
|
||||
///
|
||||
/// Frees the memory region at `pointer` that was previously
|
||||
/// allocated with [malloc].
|
||||
void free(int pointer);
|
||||
|
||||
/// Provides access to the [WebAssemblys memory](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Memory) buffer.
|
||||
///
|
||||
/// The actual [ByteBuffer] object returned by this getter is allowed to change;
|
||||
/// It should not be cached in a state variable and is thus annotated with @[doNotStore].
|
||||
@doNotStore
|
||||
ByteBuffer get heap;
|
||||
|
||||
/// A list containing everything exported by the underlying
|
||||
/// [WebAssembly instance](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Instance).
|
||||
List<WasmSymbol> get exports;
|
||||
}
|
||||
|
||||
/// Describes something exported by the WebAssembly.
|
||||
@extra
|
||||
@sealed
|
||||
abstract class WasmSymbol {
|
||||
/// The address of the exported thing.
|
||||
final int address;
|
||||
|
||||
/// The name of the exported thing.
|
||||
final String name;
|
||||
|
||||
const WasmSymbol({required this.address, required this.name});
|
||||
|
||||
@override
|
||||
int get hashCode => toString().hashCode;
|
||||
|
||||
@override
|
||||
String toString() => '[address=$address\tname=$name]';
|
||||
}
|
||||
|
||||
/// A global is a symbol exported by the WebAssembly,
|
||||
/// that is not a function.
|
||||
@extra
|
||||
@sealed
|
||||
class Global extends WasmSymbol {
|
||||
const Global({required int address, required String name})
|
||||
: super(address: address, name: name);
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
if (other != null && other is Global) {
|
||||
return name == other.name && address == other.address;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes a function exported from WebAssembly.
|
||||
@extra
|
||||
@sealed
|
||||
class FunctionDescription extends WasmSymbol {
|
||||
/// The index of this function in the [WebAssembly table](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table).
|
||||
/// This is the same as its address.
|
||||
int get tableIndex => address;
|
||||
|
||||
/// The amount of arguments the underyling function has.
|
||||
final int argumentCount;
|
||||
|
||||
/// The actual function.
|
||||
final Function function;
|
||||
const FunctionDescription(
|
||||
{required int tableIndex,
|
||||
required String name,
|
||||
required this.argumentCount,
|
||||
required this.function})
|
||||
: super(address: tableIndex, name: name);
|
||||
|
||||
@override
|
||||
int get hashCode => '$name$argumentCount$tableIndex'.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
if (other != null && other is FunctionDescription) {
|
||||
return argumentCount == other.argumentCount &&
|
||||
name == other.name &&
|
||||
tableIndex == other.tableIndex;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
'[tableIndex=$tableIndex\tname=$name\targumentCount=$argumentCount\tfunction=$function]';
|
||||
}
|
||||
20
spine-flutter/lib/web_ffi/modules/null_memory.dart
Executable file
20
spine-flutter/lib/web_ffi/modules/null_memory.dart
Executable file
@ -0,0 +1,20 @@
|
||||
import 'dart:typed_data';
|
||||
import 'memory.dart';
|
||||
import '../ffi/types.dart';
|
||||
|
||||
class NullMemory implements Memory {
|
||||
@override
|
||||
Pointer<T> allocate<T extends NativeType>(int byteCount, {int? alignment}) {
|
||||
throw new UnsupportedError(
|
||||
'Can not use the null memory to allocate space!');
|
||||
}
|
||||
|
||||
@override
|
||||
ByteBuffer get buffer =>
|
||||
throw new UnsupportedError('The null memory has no buffer!');
|
||||
|
||||
@override
|
||||
void free(Pointer<NativeType> pointer) {
|
||||
throw new UnsupportedError('Can not use the null memory to free pointers!');
|
||||
}
|
||||
}
|
||||
6
spine-flutter/lib/web_ffi/web_ffi.dart
Executable file
6
spine-flutter/lib/web_ffi/web_ffi.dart
Executable file
@ -0,0 +1,6 @@
|
||||
/// Provides mechanisms to use a [dart:ffi 2.12.0](https://api.dart.dev/stable/2.12.0/dart-ffi/dart-ffi-library.html) like API on the web but using [dart:js](https://api.dart.dev/stable/dart-js/dart-js-library.html).
|
||||
/// While some things are missing, new things were added, identifiable by the @[extra] annotation.
|
||||
library web_ffi;
|
||||
|
||||
export 'ffi/types.dart';
|
||||
export 'ffi/extensions.dart';
|
||||
4
spine-flutter/lib/web_ffi/web_ffi_meta.dart
Executable file
4
spine-flutter/lib/web_ffi/web_ffi_meta.dart
Executable file
@ -0,0 +1,4 @@
|
||||
/// This library contains and explains the annotations for `web_ffi`.
|
||||
library web_ffi_meta;
|
||||
|
||||
export 'meta/meta.dart';
|
||||
12
spine-flutter/lib/web_ffi/web_ffi_modules.dart
Executable file
12
spine-flutter/lib/web_ffi/web_ffi_modules.dart
Executable file
@ -0,0 +1,12 @@
|
||||
/// Provides additional classes that are needed for web_ffi,
|
||||
/// but are not present in [dart:ffi](https://api.dart.dev/stable/2.12.0/dart-ffi/dart-ffi-library.html).
|
||||
library web_ffi_modules;
|
||||
|
||||
export 'modules/exceptions.dart';
|
||||
export 'modules/module.dart';
|
||||
export 'modules/memory.dart'
|
||||
show registerOpaqueType, Memory, MemoryRegisterMode;
|
||||
|
||||
export 'modules/emscripten/emscripten_module_stub.dart'
|
||||
if (dart.library.js) 'modules/emscripten/emscripten_module.dart'
|
||||
show EmscriptenModule;
|
||||
@ -12,8 +12,9 @@ dependencies:
|
||||
sdk: flutter
|
||||
plugin_platform_interface: ^2.0.2
|
||||
ffi: ^2.0.1
|
||||
web_ffi: ^0.7.2
|
||||
inject_js: ^2.0.0
|
||||
js: ^0.6.3
|
||||
meta: ^1.3.0
|
||||
http: ^0.13.5
|
||||
|
||||
dev_dependencies:
|
||||
|
||||
@ -2,15 +2,21 @@
|
||||
dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
|
||||
pushd $dir > /dev/null
|
||||
mkdir -p ../lib/assets/
|
||||
# Need to use -O2, as -O3 applies the Closure compiler to native function names.
|
||||
# The entries for exported functions in Module.asm will be scrambled so
|
||||
# EmscriptenModule._fromJs() is unable to parse them and link them with original
|
||||
# names set on the module, e.g. Module._spine_get_major_version.
|
||||
emcc \
|
||||
-Ispine-cpp/include \
|
||||
--closure 1 -O3 -fno-rtti -fno-exceptions \
|
||||
-s MAIN_MODULE=1 \
|
||||
-O2 --closure 1 -fno-rtti -fno-exceptions -lc++abi -lc++ \
|
||||
-s STRICT=1 \
|
||||
-s LLD_REPORT_UNDEFINED \
|
||||
-s MODULARIZE=1 \
|
||||
-s ALLOW_MEMORY_GROWTH=1 \
|
||||
-s ALLOW_TABLE_GROWTH \
|
||||
-s MALLOC=emmalloc \
|
||||
-s ENVIRONMENT=web \
|
||||
-s EXPORT_ALL=1 \
|
||||
-s EXPORTED_FUNCTIONS='["_malloc", "_free"]' \
|
||||
--no-entry \
|
||||
-s EXPORT_NAME=libspine_flutter \
|
||||
spine_flutter.cpp `find spine-cpp/src -type f` \
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user