Kevin Gu
4 年前
当前提交
284b4aac
共有 48 个文件被更改,包括 2361 次插入 和 359 次删除
-
111com.unity.uiwidgets/Runtime/async2/future.cs
-
47com.unity.uiwidgets/Runtime/async2/future_impl.cs
-
77com.unity.uiwidgets/Runtime/async2/schedule_microtask.cs
-
44com.unity.uiwidgets/Runtime/async2/timer.cs
-
16com.unity.uiwidgets/Runtime/engine2/UIWidgetsPanel.cs
-
9com.unity.uiwidgets/Runtime/foundation/binding.cs
-
2com.unity.uiwidgets/Runtime/foundation/constants.cs
-
48com.unity.uiwidgets/Runtime/painting/binding.cs
-
10com.unity.uiwidgets/Runtime/scheduler2/binding.cs
-
6com.unity.uiwidgets/Runtime/scheduler2/ticker.cs
-
24com.unity.uiwidgets/Runtime/ui2/compositing.cs
-
138com.unity.uiwidgets/Runtime/ui2/hooks.cs
-
59com.unity.uiwidgets/Runtime/ui2/isolate.cs
-
80com.unity.uiwidgets/Runtime/ui2/painting.cs
-
73com.unity.uiwidgets/Runtime/ui2/window.cs
-
6engine/Build.bee.cs
-
93engine/src/lib/ui/window/window.cc
-
19engine/src/shell/platform/unity/uiwidgets_panel.cc
-
2engine/src/shell/platform/unity/uiwidgets_panel.h
-
9engine/src/shell/platform/unity/win32_task_runner.cc
-
33com.unity.uiwidgets/Runtime/foundation/isolates.cs
-
3com.unity.uiwidgets/Runtime/foundation/isolates.cs.meta
-
11com.unity.uiwidgets/Runtime/foundation/object.cs
-
3com.unity.uiwidgets/Runtime/foundation/object.cs.meta
-
202com.unity.uiwidgets/Runtime/foundation/serialization.cs
-
3com.unity.uiwidgets/Runtime/foundation/serialization.cs.meta
-
53com.unity.uiwidgets/Runtime/foundation/synchronous_future.cs
-
3com.unity.uiwidgets/Runtime/foundation/synchronous_future.cs.meta
-
159com.unity.uiwidgets/Runtime/painting/shader_warmup.cs
-
3com.unity.uiwidgets/Runtime/painting/shader_warmup.cs.meta
-
3com.unity.uiwidgets/Runtime/services.meta
-
158com.unity.uiwidgets/Runtime/ui2/channel_buffers.cs
-
3com.unity.uiwidgets/Runtime/ui2/channel_buffers.cs.meta
-
50engine/src/lib/ui/window/platform_message_response_mono.cc
-
33engine/src/lib/ui/window/platform_message_response_mono.h
-
161com.unity.uiwidgets/Runtime/services/asset_bundle.cs
-
3com.unity.uiwidgets/Runtime/services/asset_bundle.cs.meta
-
15com.unity.uiwidgets/Runtime/services/binary_messenger.cs
-
3com.unity.uiwidgets/Runtime/services/binary_messenger.cs.meta
-
104com.unity.uiwidgets/Runtime/services/binding.cs
-
3com.unity.uiwidgets/Runtime/services/binding.cs.meta
-
67com.unity.uiwidgets/Runtime/services/message_codec.cs
-
3com.unity.uiwidgets/Runtime/services/message_codec.cs.meta
-
650com.unity.uiwidgets/Runtime/services/message_codecs.cs
-
3com.unity.uiwidgets/Runtime/services/message_codecs.cs.meta
-
89engine/src/lib/ui/window/platform_message_response_dart.cc
-
26engine/src/lib/ui/window/platform_message_response_dart.h
|
|||
using Unity.UIWidgets.async2; |
|||
using Unity.UIWidgets.foundation; |
|||
using Unity.UIWidgets.scheduler2; |
|||
using Unity.UIWidgets.ui; |
|||
using Window = Unity.UIWidgets.ui2.Window; |
|||
// protected override void initInstances() {
|
|||
// base.initInstances();
|
|||
// instance = this;
|
|||
// _imageCache = createImageCache();
|
|||
//
|
|||
// if (shaderWarmUp != null) {
|
|||
// shaderWarmUp.execute();
|
|||
// }
|
|||
// }
|
|||
|
|||
public PaintingBinding() { |
|||
} |
|||
public static ShaderWarmUp shaderWarmUp = new DefaultShaderWarmUp(); |
|||
public ImageCache imageCache => _imageCache; |
|||
public ImageCache imageCache { |
|||
get { |
|||
if (this._imageCache == null) { |
|||
this._imageCache = this.createImageCache(); |
|||
} |
|||
|
|||
return this._imageCache; |
|||
} |
|||
} |
|||
//
|
|||
// public Future<Codec> instantiateImageCodec(byte[] bytes,
|
|||
// int? cacheWidth = null,
|
|||
// int? cacheHeight = null) {
|
|||
// D.assert(cacheWidth == null || cacheWidth > 0);
|
|||
// D.assert(cacheHeight == null || cacheHeight > 0);
|
|||
//
|
|||
// Future<object> f = instantiateImageCodec(null).then<object>(c => {
|
|||
// return FutureOr.null_;
|
|||
// }).to<Codec>().asOf<object>();
|
|||
// return ui.instantiateImageCodec(
|
|||
// bytes,
|
|||
// targetWidth: cacheWidth,
|
|||
// targetHeight: cacheHeight
|
|||
// );
|
|||
// }
|
|||
} |
|||
|
|||
public static partial class painting_ { |
|||
public static ImageCache imageCache => PaintingBinding.instance.imageCache; |
|||
} |
|||
} |
|
|||
using System.ComponentModel; |
|||
using Unity.UIWidgets.async2; |
|||
using Unity.UIWidgets.ui2; |
|||
|
|||
namespace Unity.UIWidgets.foundation { |
|||
public delegate R ComputeCallback<Q, R>(Q message); |
|||
|
|||
public static partial class foundation_ { |
|||
public static Future<R> compute<Q, R>(ComputeCallback<Q, R> callback, Q message, string debugLabel = null) { |
|||
var completer = Completer.create(); |
|||
var isolate = Isolate.current; |
|||
|
|||
var backgroundWorker = new BackgroundWorker(); |
|||
backgroundWorker.DoWork += (sender, args) => { args.Result = callback((Q) args.Argument); }; |
|||
backgroundWorker.RunWorkerCompleted += (o, a) => { |
|||
if (!isolate.isValid) { |
|||
return; |
|||
} |
|||
|
|||
using (Isolate.getScope(isolate)) { |
|||
if (a.Error != null) { |
|||
completer.completeError(a.Error); |
|||
} |
|||
else { |
|||
completer.complete(FutureOr.value((R) a.Result)); |
|||
} |
|||
} |
|||
}; |
|||
backgroundWorker.RunWorkerAsync(message); |
|||
return completer.future.to<R>(); |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 49ca84be53324d319a12e00ce498586d |
|||
timeCreated: 1600226656 |
|
|||
namespace Unity.UIWidgets.foundation { |
|||
public static partial class foundation_ { |
|||
public static string objectRuntimeType(object @object, string optimizedValue) { |
|||
D.assert(() => { |
|||
optimizedValue = @object.GetType().ToString(); |
|||
return true; |
|||
}); |
|||
return optimizedValue; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 22e45882692145ac80b92b36da3a9a95 |
|||
timeCreated: 1600152523 |
|
|||
using System; |
|||
|
|||
namespace Unity.UIWidgets.foundation { |
|||
public class WriteBuffer { |
|||
public WriteBuffer() { |
|||
_buffer = new byte[0]; |
|||
_position = 0; |
|||
} |
|||
|
|||
byte[] _buffer; |
|||
int _position; |
|||
|
|||
void _ensureCapacity(int value) { |
|||
if (value <= _buffer.Length) |
|||
return; |
|||
int capacity = value; |
|||
if (capacity < 256) |
|||
capacity = 256; |
|||
if (capacity < _buffer.Length * 2) |
|||
capacity = _buffer.Length * 2; |
|||
if ((uint) (_buffer.Length * 2) > 2147483591U) |
|||
capacity = value > 2147483591 ? value : 2147483591; |
|||
|
|||
byte[] buffer = new byte[capacity]; |
|||
if (_buffer.Length > 0) |
|||
Buffer.BlockCopy(_buffer, 0, buffer, 0, _buffer.Length); |
|||
_buffer = buffer; |
|||
} |
|||
|
|||
public void putUint8(byte value) { |
|||
_ensureCapacity(_position + 1); |
|||
_buffer[_position] = value; |
|||
_position++; |
|||
} |
|||
|
|||
public unsafe void putUint16(ushort value) { |
|||
_ensureCapacity(_position + 2); |
|||
fixed (byte* ptr = &_buffer[_position]) |
|||
*(ushort*) ptr = value; |
|||
_position += 2; |
|||
} |
|||
|
|||
public unsafe void putUint32(uint value) { |
|||
_ensureCapacity(_position + 4); |
|||
fixed (byte* ptr = &_buffer[_position]) |
|||
*(uint*) ptr = value; |
|||
_position += 4; |
|||
} |
|||
|
|||
public unsafe void putInt32(int value) { |
|||
_ensureCapacity(_position + 4); |
|||
fixed (byte* ptr = &_buffer[_position]) |
|||
*(int*) ptr = value; |
|||
_position += 4; |
|||
} |
|||
|
|||
public unsafe void putInt64(long value) { |
|||
_ensureCapacity(_position + 8); |
|||
fixed (byte* ptr = &_buffer[_position]) |
|||
*(long*) ptr = value; |
|||
_position += 8; |
|||
} |
|||
|
|||
public unsafe void putFloat32(float value) { |
|||
_alignTo(4); |
|||
_ensureCapacity(_position + 4); |
|||
fixed (byte* ptr = &_buffer[_position]) |
|||
*(float*) ptr = value; |
|||
_position += 4; |
|||
} |
|||
|
|||
public void putUint8List(byte[] list) { |
|||
int lengthInBytes = list.Length; |
|||
_ensureCapacity(_position + lengthInBytes); |
|||
Buffer.BlockCopy(list, 0, _buffer, _position, lengthInBytes); |
|||
_position += lengthInBytes; |
|||
} |
|||
|
|||
public void putInt32List(int[] list) { |
|||
_alignTo(4); |
|||
int lengthInBytes = list.Length * 4; |
|||
_ensureCapacity(_position + lengthInBytes); |
|||
Buffer.BlockCopy(list, 0, _buffer, _position, lengthInBytes); |
|||
_position += lengthInBytes; |
|||
} |
|||
|
|||
public void putInt64List(long[] list) { |
|||
_alignTo(8); |
|||
int lengthInBytes = list.Length * 8; |
|||
_ensureCapacity(_position + lengthInBytes); |
|||
Buffer.BlockCopy(list, 0, _buffer, _position, lengthInBytes); |
|||
_position += lengthInBytes; |
|||
} |
|||
|
|||
public void putFloat32List(float[] list) { |
|||
_alignTo(4); |
|||
int lengthInBytes = list.Length * 4; |
|||
_ensureCapacity(_position + lengthInBytes); |
|||
Buffer.BlockCopy(list, 0, _buffer, _position, lengthInBytes); |
|||
_position += lengthInBytes; |
|||
} |
|||
|
|||
void _alignTo(int alignment) { |
|||
var mod = _position % alignment; |
|||
if (mod != 0) { |
|||
for (int i = 0; i < alignment - mod; i++) |
|||
putUint8(0); |
|||
} |
|||
} |
|||
|
|||
public byte[] done() { |
|||
var bytes = new byte[_position]; |
|||
Buffer.BlockCopy(_buffer, 0, bytes, 0, _position); |
|||
_buffer = null; |
|||
return bytes; |
|||
} |
|||
} |
|||
|
|||
public class ReadBuffer { |
|||
public ReadBuffer(byte[] data) { |
|||
D.assert(data != null); |
|||
this.data = data; |
|||
} |
|||
|
|||
public readonly byte[] data; |
|||
|
|||
int _position = 0; |
|||
|
|||
public bool hasRemaining => _position < data.Length; |
|||
|
|||
public byte getUint8() { |
|||
return data[_position++]; |
|||
} |
|||
|
|||
public unsafe ushort getUint16() { |
|||
fixed (byte* ptr = &data[_position += 2]) |
|||
return *(ushort*) ptr; |
|||
} |
|||
|
|||
public unsafe uint getUint32() { |
|||
fixed (byte* ptr = &data[_position += 4]) |
|||
return *(uint*) ptr; |
|||
} |
|||
|
|||
public unsafe int getInt32() { |
|||
fixed (byte* ptr = &data[_position += 4]) |
|||
return *(int*) ptr; |
|||
} |
|||
|
|||
public unsafe long getInt64() { |
|||
fixed (byte* ptr = &data[_position += 8]) |
|||
return *(long*) ptr; |
|||
} |
|||
|
|||
public unsafe float getFloat32() { |
|||
_alignTo(4); |
|||
fixed (byte* ptr = &data[_position += 4]) |
|||
return *(float*) ptr; |
|||
} |
|||
|
|||
public byte[] getUint8List(int length) { |
|||
byte[] list = new byte[length]; |
|||
int lengthInBytes = length; |
|||
Buffer.BlockCopy(data, _position, list, 0, lengthInBytes); |
|||
_position += lengthInBytes; |
|||
return list; |
|||
} |
|||
|
|||
public int[] getInt32List(int length) { |
|||
_alignTo(4); |
|||
int[] list = new int[length]; |
|||
int lengthInBytes = length * 4; |
|||
Buffer.BlockCopy(data, _position, list, 0, lengthInBytes); |
|||
_position += lengthInBytes; |
|||
return list; |
|||
} |
|||
|
|||
public long[] getInt64List(int length) { |
|||
_alignTo(8); |
|||
long[] list = new long[length]; |
|||
int lengthInBytes = length * 8; |
|||
Buffer.BlockCopy(data, _position, list, 0, lengthInBytes); |
|||
_position += lengthInBytes; |
|||
return list; |
|||
} |
|||
|
|||
public float[] getFloat32List(int length) { |
|||
_alignTo(4); |
|||
float[] list = new float[length]; |
|||
int lengthInBytes = length * 4; |
|||
Buffer.BlockCopy(data, _position, list, 0, lengthInBytes); |
|||
_position += lengthInBytes; |
|||
return list; |
|||
} |
|||
|
|||
void _alignTo(int alignment) { |
|||
int mod = _position % alignment; |
|||
if (mod != 0) |
|||
_position += alignment - mod; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 400db01fc64b47479eae0c633ef6b721 |
|||
timeCreated: 1599625046 |
|
|||
using System; |
|||
using Unity.UIWidgets.async2; |
|||
|
|||
namespace Unity.UIWidgets.foundation { |
|||
public class SynchronousFuture : Future { |
|||
public SynchronousFuture(object value) { |
|||
_value = value; |
|||
} |
|||
|
|||
readonly object _value; |
|||
|
|||
// @override
|
|||
// Stream<T> asStream() {
|
|||
// final StreamController<T> controller = StreamController<T>();
|
|||
// controller.add(_value);
|
|||
// controller.close();
|
|||
// return controller.stream;
|
|||
// }
|
|||
|
|||
public override Future catchError(Func<Exception, FutureOr> onError, Func<Exception, bool> test = null) { |
|||
return Completer.create().future; |
|||
} |
|||
|
|||
public override Future then(Func<object, FutureOr> f, Func<Exception, FutureOr> onError = null) { |
|||
FutureOr result = f(_value); |
|||
if (result.isFuture) |
|||
return result.f; |
|||
|
|||
return new SynchronousFuture(result.v); |
|||
} |
|||
|
|||
public override Future timeout(TimeSpan timeLimit, Func<FutureOr> onTimeout = null) { |
|||
return Future.value(FutureOr.value(_value)).timeout(timeLimit, onTimeout: onTimeout); |
|||
} |
|||
|
|||
public override Future whenComplete(Func<FutureOr> action) { |
|||
try { |
|||
FutureOr result = action(); |
|||
if (result.isFuture) |
|||
return result.f.then((value) => FutureOr.value(_value)); |
|||
return this; |
|||
} |
|||
catch (Exception e) { |
|||
return Future.error(e); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public class SynchronousFuture<T> : Future<T> { |
|||
public SynchronousFuture(T value) : base(new SynchronousFuture(value)) { |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 00836efc8fdd45bf800d682996a7952a |
|||
timeCreated: 1600228109 |
|
|||
using System.Collections.Generic; |
|||
using Unity.UIWidgets.async2; |
|||
using Unity.UIWidgets.ui; |
|||
using Canvas = Unity.UIWidgets.ui2.Canvas; |
|||
using Paint = Unity.UIWidgets.ui2.Paint; |
|||
using PaintingStyle = Unity.UIWidgets.ui2.PaintingStyle; |
|||
using Path = Unity.UIWidgets.ui2.Path; |
|||
using Picture = Unity.UIWidgets.ui2.Picture; |
|||
using PictureRecorder = Unity.UIWidgets.ui2.PictureRecorder; |
|||
using Color = Unity.UIWidgets.ui2.Color; |
|||
|
|||
namespace Unity.UIWidgets.painting { |
|||
public abstract class ShaderWarmUp { |
|||
protected ShaderWarmUp() { |
|||
} |
|||
|
|||
public virtual Size size => new Size(100.0f, 100.0f); |
|||
protected abstract Future warmUpOnCanvas(Canvas canvas); |
|||
|
|||
public Future execute() { |
|||
PictureRecorder recorder = new PictureRecorder(); |
|||
Canvas canvas = new Canvas(recorder); |
|||
|
|||
return warmUpOnCanvas(canvas).then(_ => { |
|||
Picture picture = recorder.endRecording(); |
|||
//TimelineTask shaderWarmUpTask = TimelineTask();
|
|||
//shaderWarmUpTask.start('Warm-up shader');
|
|||
|
|||
picture.toImage(size.width.ceil(), size.height.ceil()).then(__ => { |
|||
//shaderWarmUpTask.finish();
|
|||
return FutureOr.nil; |
|||
}); |
|||
return FutureOr.nil; |
|||
}); |
|||
} |
|||
} |
|||
|
|||
public class DefaultShaderWarmUp : ShaderWarmUp { |
|||
public DefaultShaderWarmUp( |
|||
float drawCallSpacing = 0.0f, |
|||
Size canvasSize = null |
|||
) { |
|||
canvasSize = canvasSize ?? new Size(100.0f, 100.0f); |
|||
this.drawCallSpacing = drawCallSpacing; |
|||
this.canvasSize = canvasSize; |
|||
} |
|||
|
|||
public readonly float drawCallSpacing; |
|||
|
|||
public readonly Size canvasSize; |
|||
|
|||
public override Size size => canvasSize; |
|||
|
|||
/// Trigger common draw operations on a canvas to warm up GPU shader
|
|||
/// compilation cache.
|
|||
protected override Future warmUpOnCanvas(Canvas canvas) { |
|||
RRect rrect = RRect.fromLTRBXY(20.0f, 20.0f, 60.0f, 60.0f, 10.0f, 10.0f); |
|||
Path rrectPath = new Path(); |
|||
rrectPath.addRRect(rrect); |
|||
|
|||
Path circlePath = new Path(); |
|||
circlePath.addOval( |
|||
Rect.fromCircle(center: new Offset(40.0f, 40.0f), radius: 20.0f) |
|||
); |
|||
|
|||
// The following path is based on
|
|||
// https://skia.org/user/api/SkCanvas_Reference#SkCanvas_drawPath
|
|||
Path path = new Path(); |
|||
path.moveTo(20.0f, 60.0f); |
|||
path.quadraticBezierTo(60.0f, 20.0f, 60.0f, 60.0f); |
|||
path.close(); |
|||
path.moveTo(60.0f, 20.0f); |
|||
path.quadraticBezierTo(60.0f, 60.0f, 20.0f, 60.0f); |
|||
|
|||
Path convexPath = new Path(); |
|||
convexPath.moveTo(20.0f, 30.0f); |
|||
convexPath.lineTo(40.0f, 20.0f); |
|||
convexPath.lineTo(60.0f, 30.0f); |
|||
convexPath.lineTo(60.0f, 60.0f); |
|||
convexPath.lineTo(20.0f, 60.0f); |
|||
convexPath.close(); |
|||
|
|||
// Skia uses different shaders based on the kinds of paths being drawn and
|
|||
// the associated paint configurations. According to our experience and
|
|||
// tracing, drawing the following paths/paints generates various of
|
|||
// shaders that are commonly used.
|
|||
List<Path> paths = new List<Path> {rrectPath, circlePath, path, convexPath}; |
|||
|
|||
List<Paint> paints = new List<Paint> { |
|||
new Paint { |
|||
isAntiAlias = true, |
|||
style = PaintingStyle.fill |
|||
}, |
|||
new Paint { |
|||
isAntiAlias = false, |
|||
style = PaintingStyle.fill |
|||
}, |
|||
new Paint { |
|||
isAntiAlias = true, |
|||
style = PaintingStyle.stroke, |
|||
strokeWidth = 10 |
|||
}, |
|||
new Paint { |
|||
isAntiAlias = true, |
|||
style = PaintingStyle.stroke, |
|||
strokeWidth = 0.1f, // hairline
|
|||
} |
|||
}; |
|||
|
|||
// Warm up path stroke and fill shaders.
|
|||
for (int i = 0; i < paths.Count; i += 1) { |
|||
canvas.save(); |
|||
foreach (var paint in paints) { |
|||
canvas.drawPath(paths[i], paint); |
|||
canvas.translate(drawCallSpacing, 0.0f); |
|||
} |
|||
|
|||
canvas.restore(); |
|||
canvas.translate(0.0f, drawCallSpacing); |
|||
} |
|||
|
|||
// Warm up shadow shaders.
|
|||
Color black = new Color(0xFF000000); |
|||
canvas.save(); |
|||
canvas.drawShadow(rrectPath, black, 10.0f, true); |
|||
canvas.translate(drawCallSpacing, 0.0f); |
|||
canvas.drawShadow(rrectPath, black, 10.0f, false); |
|||
canvas.restore(); |
|||
|
|||
// Warm up text shaders.
|
|||
canvas.translate(0.0f, drawCallSpacing); |
|||
|
|||
// final ui.ParagraphBuilder paragraphBuilder = ui.ParagraphBuilder(
|
|||
// ui.ParagraphStyle(textDirection: ui.TextDirection.ltr),
|
|||
// )..pushStyle(ui.TextStyle(color: black))..addText('_');
|
|||
// final ui.Paragraph paragraph = paragraphBuilder.build()
|
|||
// ..layout(const ui.ParagraphConstraints (width: 60.0));
|
|||
// canvas.drawParagraph(paragraph, const ui.Offset (20.0, 20.0));
|
|||
|
|||
// Draw a rect inside a rrect with a non-trivial intersection. If the
|
|||
// intersection is trivial (e.g., equals the rrect clip), Skia will optimize
|
|||
// the clip out.
|
|||
//
|
|||
// Add an integral or fractional translation to trigger Skia's non-AA or AA
|
|||
// optimizations (as did before in normal FillRectOp in rrect clip cases).
|
|||
foreach (var fraction in new[] {0.0f, 0.5f}) { |
|||
canvas.save(); |
|||
canvas.translate(fraction, fraction); |
|||
canvas.clipRRect(RRect.fromLTRBR(8, 8, 328, 248, Radius.circular(16))); |
|||
canvas.drawRect(Rect.fromLTRB(10, 10, 320, 240), new Paint()); |
|||
canvas.restore(); |
|||
canvas.translate(drawCallSpacing, 0.0f); |
|||
} |
|||
|
|||
canvas.translate(0.0f, drawCallSpacing); |
|||
return Future.value(); |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 267de2236edc47de871b74a567a94a68 |
|||
timeCreated: 1599468005 |
|
|||
fileFormatVersion: 2 |
|||
guid: 6c09aa2afc094af0b1e04e39c0aa59a6 |
|||
timeCreated: 1599611392 |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
using Unity.UIWidgets.async2; |
|||
using UnityEngine; |
|||
|
|||
namespace Unity.UIWidgets.ui2 { |
|||
readonly struct _StoredMessage { |
|||
internal _StoredMessage(byte[] data, PlatformMessageResponseCallback callback) { |
|||
_data = data; |
|||
_callback = callback; |
|||
} |
|||
|
|||
readonly byte[] _data; |
|||
public byte[] data => _data; |
|||
|
|||
readonly PlatformMessageResponseCallback _callback; |
|||
public PlatformMessageResponseCallback callback => _callback; |
|||
} |
|||
|
|||
class _RingBuffer<T> where T : struct { |
|||
readonly Queue<T> _queue; |
|||
|
|||
internal _RingBuffer(int capacity) { |
|||
_capacity = capacity; |
|||
_queue = new Queue<T>(_capacity); |
|||
} |
|||
|
|||
public int length => _queue.Count; |
|||
|
|||
int _capacity; |
|||
public int capacity => _capacity; |
|||
|
|||
public bool isEmpty => _queue.Count == 0; |
|||
|
|||
Action<T> _dropItemCallback; |
|||
|
|||
public Action<T> dropItemCallback { |
|||
set { _dropItemCallback = value; } |
|||
} |
|||
|
|||
public bool push(in T val) { |
|||
if (_capacity <= 0) |
|||
return true; |
|||
|
|||
int overflowCount = _dropOverflowItems(_capacity - 1); |
|||
_queue.Enqueue(val); |
|||
return overflowCount > 0; |
|||
} |
|||
|
|||
public T? pop() { |
|||
return _queue.Count == 0 ? (T?) null : _queue.Dequeue(); |
|||
} |
|||
|
|||
int _dropOverflowItems(int lengthLimit) { |
|||
int result = 0; |
|||
while (_queue.Count > lengthLimit) { |
|||
T item = _queue.Dequeue(); |
|||
_dropItemCallback?.Invoke(item); |
|||
|
|||
result += 1; |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
public int resize(int newSize) { |
|||
_capacity = newSize; |
|||
return _dropOverflowItems(newSize); |
|||
} |
|||
} |
|||
|
|||
public delegate Future DrainChannelCallback(byte[] data, PlatformMessageResponseCallback callback); |
|||
|
|||
public class ChannelBuffers { |
|||
public const int kDefaultBufferSize = 1; |
|||
|
|||
public const string kControlChannelName = "dev.uiwidgets/channel-buffers"; |
|||
|
|||
readonly Dictionary<string, _RingBuffer<_StoredMessage>> _messages = |
|||
new Dictionary<string, _RingBuffer<_StoredMessage>>(); |
|||
|
|||
_RingBuffer<_StoredMessage> _makeRingBuffer(int size) => |
|||
new _RingBuffer<_StoredMessage>(size) {dropItemCallback = _onDropItem}; |
|||
|
|||
void _onDropItem(_StoredMessage message) { |
|||
message.callback(null); |
|||
} |
|||
|
|||
public bool push(string channel, byte[] data, PlatformMessageResponseCallback callback) { |
|||
_RingBuffer<_StoredMessage> queue = _messages[channel]; |
|||
if (queue == null) { |
|||
queue = _makeRingBuffer(kDefaultBufferSize); |
|||
_messages[channel] = queue; |
|||
} |
|||
|
|||
bool didOverflow = queue.push(new _StoredMessage(data, callback)); |
|||
if (didOverflow) { |
|||
Debug.LogWarning($"Overflow on channel: {channel}. " + |
|||
"Messages on this channel are being discarded in FIFO fashion. " + |
|||
"The engine may not be running or you need to adjust " + |
|||
"the buffer size of the channel."); |
|||
} |
|||
|
|||
return didOverflow; |
|||
} |
|||
|
|||
_StoredMessage? _pop(string channel) { |
|||
_RingBuffer<_StoredMessage> queue = _messages[channel]; |
|||
_StoredMessage? result = queue?.pop(); |
|||
return result; |
|||
} |
|||
|
|||
void _resize(string channel, int newSize) { |
|||
_RingBuffer<_StoredMessage> queue = _messages[channel]; |
|||
if (queue == null) { |
|||
queue = _makeRingBuffer(newSize); |
|||
_messages[channel] = queue; |
|||
} |
|||
else { |
|||
int numberOfDroppedMessages = queue.resize(newSize); |
|||
if (numberOfDroppedMessages > 0) { |
|||
Debug.LogWarning( |
|||
$"Dropping messages on channel \"{channel}\" as a result of shrinking the buffer size."); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public Future drain(string channel, DrainChannelCallback callback) { |
|||
return Future.doWhile(() => { |
|||
_StoredMessage? message = _pop(channel); |
|||
if (!message.HasValue) { |
|||
return false; |
|||
} |
|||
|
|||
return callback(message.Value.data, message.Value.callback); |
|||
}); |
|||
} |
|||
|
|||
string _getString(byte[] data) { |
|||
return Encoding.UTF8.GetString(data); |
|||
} |
|||
|
|||
public void handleMessage(byte[] data) { |
|||
var command = _getString(data).Split('\r'); |
|||
if (command.Length == /*arity=*/2 + 1 && command[0] == "resize") { |
|||
_resize(command[1], int.Parse(command[2])); |
|||
} |
|||
else { |
|||
throw new Exception($"Unrecognized command {command} sent to {kControlChannelName}."); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public static partial class ui_ { |
|||
public static readonly ChannelBuffers channelBuffers = new ChannelBuffers(); |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 8b23ea154160494fba86a467f9c29489 |
|||
timeCreated: 1599613180 |
|
|||
#include "platform_message_response_mono.h"
|
|||
|
|||
#include <utility>
|
|||
|
|||
#include "common/task_runners.h"
|
|||
#include "flutter/fml/make_copyable.h"
|
|||
#include "lib/ui/window/window.h"
|
|||
|
|||
namespace uiwidgets { |
|||
|
|||
PlatformMessageResponseMono::PlatformMessageResponseMono( |
|||
std::weak_ptr<MonoState> mono_state_weak, PlatformMessageCallback callback, |
|||
Mono_Handle handle, fml::RefPtr<fml::TaskRunner> ui_task_runner) |
|||
: mono_state_weak_(mono_state_weak), |
|||
callback_(std::move(callback)), |
|||
handle_(handle), |
|||
ui_task_runner_(std::move(ui_task_runner)) {} |
|||
|
|||
PlatformMessageResponseMono::~PlatformMessageResponseMono() {} |
|||
|
|||
void PlatformMessageResponseMono::Complete(std::unique_ptr<fml::Mapping> data) { |
|||
if (callback_ == nullptr) return; |
|||
FML_DCHECK(!is_complete_); |
|||
is_complete_ = true; |
|||
ui_task_runner_->PostTask(fml::MakeCopyable( |
|||
[mono_state_weak = mono_state_weak_, callback = callback_, |
|||
handle = handle_, data = std::move(data)]() { |
|||
const std::shared_ptr<MonoState> mono_state = mono_state_weak.lock(); |
|||
if (!mono_state) return; |
|||
MonoState::Scope scope(mono_state); |
|||
|
|||
callback(handle, data->GetMapping(), static_cast<int>(data->GetSize())); |
|||
})); |
|||
} |
|||
|
|||
void PlatformMessageResponseMono::CompleteEmpty() { |
|||
if (callback_ == nullptr) return; |
|||
FML_DCHECK(!is_complete_); |
|||
is_complete_ = true; |
|||
ui_task_runner_->PostTask([mono_state_weak = mono_state_weak_, |
|||
callback = callback_, handle = handle_]() { |
|||
const std::shared_ptr<MonoState> mono_state = mono_state_weak.lock(); |
|||
if (!mono_state) return; |
|||
MonoState::Scope scope(mono_state); |
|||
|
|||
callback(handle, nullptr, 0); |
|||
}); |
|||
} |
|||
|
|||
} // namespace uiwidgets
|
|
|||
#pragma once |
|||
|
|||
#include "flutter/fml/message_loop.h" |
|||
#include "platform_message_response.h" |
|||
#include "runtime/mono_state.h" |
|||
|
|||
namespace uiwidgets { |
|||
|
|||
class PlatformMessageResponseMono : public PlatformMessageResponse { |
|||
FML_FRIEND_MAKE_REF_COUNTED(PlatformMessageResponseMono); |
|||
|
|||
public: |
|||
void Complete(std::unique_ptr<fml::Mapping> data) override; |
|||
void CompleteEmpty() override; |
|||
|
|||
typedef void (*PlatformMessageCallback)(Mono_Handle callback_handle, |
|||
const uint8_t* data, int data_length); |
|||
|
|||
protected: |
|||
explicit PlatformMessageResponseMono( |
|||
std::weak_ptr<MonoState> mono_state_weak, |
|||
PlatformMessageCallback callback, Mono_Handle handle, |
|||
fml::RefPtr<fml::TaskRunner> ui_task_runner); |
|||
~PlatformMessageResponseMono() override; |
|||
|
|||
std::weak_ptr<MonoState> mono_state_weak_; |
|||
PlatformMessageCallback callback_; |
|||
Mono_Handle handle_; |
|||
|
|||
fml::RefPtr<fml::TaskRunner> ui_task_runner_; |
|||
}; |
|||
|
|||
} // namespace uiwidgets |
|
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
using Unity.UIWidgets.async2; |
|||
using Unity.UIWidgets.engine2; |
|||
using Unity.UIWidgets.foundation; |
|||
using Unity.UIWidgets.ui2; |
|||
using UnityEngine.Networking; |
|||
|
|||
namespace Unity.UIWidgets.services { |
|||
public abstract class AssetBundle { |
|||
public abstract Future<byte[]> load(string key); |
|||
|
|||
public virtual Future<string> loadString(string key, bool cache = true) { |
|||
return load(key).then<string>(data => { |
|||
if (data == null) |
|||
throw new UIWidgetsError($"Unable to load asset: {key}"); |
|||
|
|||
if (data.Length < 10 * 1024) { |
|||
// 10KB takes about 3ms to parse on a Pixel 2 XL.
|
|||
// See: https://github.com/dart-lang/sdk/issues/31954
|
|||
return Encoding.UTF8.GetString(data); |
|||
} |
|||
|
|||
return foundation_.compute(Encoding.UTF8.GetString, data, debugLabel: $"UTF8 decode for \"{key}\""); |
|||
}); |
|||
} |
|||
|
|||
public abstract Future<T> loadStructuredData<T>(String key, Func<string, Future<T>> parser); |
|||
|
|||
public virtual void evict(String key) { |
|||
} |
|||
|
|||
public override string ToString() => $"{Diagnostics.describeIdentity(this)}()"; |
|||
} |
|||
|
|||
public class NetworkAssetBundle : AssetBundle { |
|||
public NetworkAssetBundle(Uri baseUrl, IDictionary<string, string> headers = null) { |
|||
_baseUrl = baseUrl; |
|||
this.headers = headers; |
|||
} |
|||
|
|||
public readonly IDictionary<string, string> headers; |
|||
|
|||
readonly Uri _baseUrl; |
|||
|
|||
Uri _urlFromKey(string key) => new Uri(_baseUrl, key); |
|||
|
|||
IEnumerator _loadCoroutine(string key, Completer completer, Isolate isolate) { |
|||
var url = _urlFromKey(key); |
|||
using (var www = UnityWebRequest.Get(url)) { |
|||
if (headers != null) { |
|||
foreach (var header in headers) { |
|||
www.SetRequestHeader(header.Key, header.Value); |
|||
} |
|||
} |
|||
|
|||
yield return www.SendWebRequest(); |
|||
|
|||
if (www.isNetworkError || www.isHttpError) { |
|||
completer.completeError(new Exception($"Failed to load from url \"{url}\": {www.error}")); |
|||
yield break; |
|||
} |
|||
|
|||
var data = www.downloadHandler.data; |
|||
|
|||
using (Isolate.getScope(isolate)) { |
|||
completer.complete(data); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public override Future<byte[]> load(string key) { |
|||
var completer = Completer.create(); |
|||
var isolate = Isolate.current; |
|||
var panel = UIWidgetsPanel.current; |
|||
panel.StartCoroutine(_loadCoroutine(key, completer, isolate)); |
|||
return completer.future.to<byte[]>(); |
|||
} |
|||
|
|||
public override Future<T> loadStructuredData<T>(string key, Func<string, Future<T>> parser) { |
|||
D.assert(key != null); |
|||
D.assert(parser != null); |
|||
return loadString(key).then<T>(value => parser(value)); |
|||
} |
|||
|
|||
public override string ToString() => $"{Diagnostics.describeIdentity(this)}({_baseUrl})"; |
|||
} |
|||
|
|||
|
|||
public abstract class CachingAssetBundle : AssetBundle { |
|||
readonly Dictionary<string, Future<string>> _stringCache = new Dictionary<string, Future<string>>(); |
|||
readonly Dictionary<string, Future> _structuredDataCache = new Dictionary<string, Future>(); |
|||
|
|||
public override Future<string> loadString(string key, bool cache = true) { |
|||
if (cache) |
|||
return _stringCache.putIfAbsent(key, () => base.loadString(key)); |
|||
return base.loadString(key); |
|||
} |
|||
|
|||
public override Future<T> loadStructuredData<T>(String key, Func<string, Future<T>> parser) { |
|||
D.assert(key != null); |
|||
D.assert(parser != null); |
|||
if (_structuredDataCache.ContainsKey(key)) |
|||
return _structuredDataCache[key].to<T>(); |
|||
|
|||
Completer completer = null; |
|||
Future<T> result = null; |
|||
loadString(key, cache: false).then<T>(value => parser(value)).then<object>((T value) => { |
|||
result = new SynchronousFuture<T>(value); |
|||
_structuredDataCache[key] = result; |
|||
if (completer != null) { |
|||
// We already returned from the loadStructuredData function, which means
|
|||
// we are in the asynchronous mode. Pass the value to the completer. The
|
|||
// completer's future is what we returned.
|
|||
completer.complete(FutureOr.value(value)); |
|||
} |
|||
|
|||
return FutureOr.nil; |
|||
}); |
|||
|
|||
if (result != null) { |
|||
// The code above ran synchronously, and came up with an answer.
|
|||
// Return the SynchronousFuture that we created above.
|
|||
return result; |
|||
} |
|||
|
|||
// The code above hasn't yet run its "then" handler yet. Let's prepare a
|
|||
// completer for it to use when it does run.
|
|||
completer = Completer.create(); |
|||
_structuredDataCache[key] = result = completer.future.to<T>(); |
|||
return result; |
|||
} |
|||
|
|||
public override void evict(string key) { |
|||
_stringCache.Remove(key); |
|||
_structuredDataCache.Remove(key); |
|||
} |
|||
} |
|||
|
|||
public class PlatformAssetBundle : CachingAssetBundle { |
|||
public override Future<byte[]> load(string key) { |
|||
byte[] encoded = Encoding.UTF8.GetBytes(key); |
|||
return ServicesBinding.instance.defaultBinaryMessenger.send( |
|||
"uiwidgets/assets", encoded).then<byte[]>(asset => { |
|||
if (asset == null) |
|||
throw new UIWidgetsError($"Unable to load asset: {key}"); |
|||
return asset; |
|||
}); |
|||
} |
|||
} |
|||
|
|||
public static partial class services_ { |
|||
static AssetBundle _initRootBundle() { |
|||
return new PlatformAssetBundle(); |
|||
} |
|||
|
|||
public static readonly AssetBundle rootBundle = _initRootBundle(); |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 140612c411134f2791d26fb58b86fc25 |
|||
timeCreated: 1600225242 |
|
|||
using System; |
|||
using Unity.UIWidgets.async2; |
|||
using Unity.UIWidgets.ui2; |
|||
|
|||
namespace Unity.UIWidgets.services { |
|||
public delegate Future<byte[]> MessageHandler(byte[] message); |
|||
|
|||
public interface BinaryMessenger { |
|||
Future handlePlatformMessage(string channel, byte[] data, PlatformMessageResponseCallback callback); |
|||
|
|||
Future<byte[]> send(string channel, byte[] message); |
|||
|
|||
void setMessageHandler(string channel, MessageHandler handler); |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 541c41d108bd4f699a27646da8b52a38 |
|||
timeCreated: 1599611703 |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Unity.UIWidgets.async2; |
|||
using Unity.UIWidgets.foundation; |
|||
using Unity.UIWidgets.ui2; |
|||
|
|||
namespace Unity.UIWidgets.services { |
|||
public class ServicesBinding : BindingBase { |
|||
protected override void initInstances() { |
|||
base.initInstances(); |
|||
instance = this; |
|||
|
|||
_defaultBinaryMessenger = createBinaryMessenger(); |
|||
window.onPlatformMessage = defaultBinaryMessenger.handlePlatformMessage; |
|||
//initLicenses();
|
|||
//SystemChannels.system.setMessageHandler(handleSystemMessage);
|
|||
} |
|||
|
|||
public static ServicesBinding instance { |
|||
get { return (ServicesBinding) Window.instance._binding; } |
|||
private set { Window.instance._binding = value; } |
|||
} |
|||
|
|||
public BinaryMessenger defaultBinaryMessenger => _defaultBinaryMessenger; |
|||
BinaryMessenger _defaultBinaryMessenger; |
|||
|
|||
protected BinaryMessenger createBinaryMessenger() { |
|||
return new _DefaultBinaryMessenger(); |
|||
} |
|||
|
|||
protected virtual Future handleSystemMessage(Object systemMessage) { |
|||
return Future.value(); |
|||
} |
|||
|
|||
protected virtual void evict(string asset) { |
|||
services_.rootBundle.evict(asset); |
|||
} |
|||
} |
|||
|
|||
|
|||
class _DefaultBinaryMessenger : BinaryMessenger { |
|||
internal _DefaultBinaryMessenger() { |
|||
} |
|||
|
|||
readonly Dictionary<string, MessageHandler> _handlers = new Dictionary<string, MessageHandler>(); |
|||
|
|||
Future<byte[]> _sendPlatformMessage(string channel, byte[] message) { |
|||
Completer completer = Completer.create(); |
|||
|
|||
Window.instance.sendPlatformMessage(channel, message, (reply) => { |
|||
try { |
|||
completer.complete(FutureOr.value(reply)); |
|||
} |
|||
catch (Exception exception) { |
|||
UIWidgetsError.reportError(new UIWidgetsErrorDetails( |
|||
exception: exception, |
|||
library: "services library", |
|||
context: "during a platform message response callback" |
|||
)); |
|||
} |
|||
}); |
|||
|
|||
return completer.future.to<byte[]>(); |
|||
} |
|||
|
|||
public Future handlePlatformMessage( |
|||
string channel, byte[] data, |
|||
PlatformMessageResponseCallback callback) { |
|||
MessageHandler handler = _handlers[channel]; |
|||
if (handler == null) { |
|||
ui_.channelBuffers.push(channel, data, callback); |
|||
return Future.value(); |
|||
} |
|||
|
|||
return handler(data).then(bytes => { |
|||
var response = (byte[]) bytes; |
|||
callback(response); |
|||
return FutureOr.nil; |
|||
}, onError: exception => { |
|||
UIWidgetsError.reportError(new UIWidgetsErrorDetails( |
|||
exception: exception, |
|||
library: "services library", |
|||
context: "during a platform message callback") |
|||
); |
|||
callback(null); |
|||
return FutureOr.nil; |
|||
}); |
|||
} |
|||
|
|||
public Future<byte[]> send(string channel, byte[] message) { |
|||
return _sendPlatformMessage(channel, message); |
|||
} |
|||
|
|||
public void setMessageHandler(string channel, MessageHandler handler) { |
|||
if (handler == null) |
|||
_handlers.Remove(channel); |
|||
else |
|||
_handlers[channel] = handler; |
|||
ui_.channelBuffers.drain(channel, |
|||
(byte[] data, PlatformMessageResponseCallback callback) => |
|||
handlePlatformMessage(channel, data, callback)); |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: e11e2e204ab94b0790425f27f98c8b73 |
|||
timeCreated: 1599611404 |
|
|||
using System; |
|||
using Unity.UIWidgets.external.simplejson; |
|||
using Unity.UIWidgets.foundation; |
|||
|
|||
namespace Unity.UIWidgets.services { |
|||
public interface MessageCodec<T> { |
|||
byte[] encodeMessage(T message); |
|||
|
|||
T decodeMessage(byte[] message); |
|||
} |
|||
|
|||
public readonly struct MethodCall { |
|||
public MethodCall(string method, object arguments) { |
|||
D.assert(method != null); |
|||
this.method = method; |
|||
this.arguments = arguments; |
|||
} |
|||
|
|||
public readonly string method; |
|||
|
|||
public readonly object arguments; |
|||
|
|||
public override string ToString() => |
|||
$"{foundation_.objectRuntimeType(this, "MethodCall")}({method}, {arguments})"; |
|||
} |
|||
|
|||
public interface MethodCodec { |
|||
byte[] encodeMethodCall(MethodCall methodCall); |
|||
|
|||
MethodCall decodeMethodCall(byte[] methodCall); |
|||
|
|||
object decodeEnvelope(byte[] envelope); |
|||
|
|||
byte[] encodeSuccessEnvelope(object result); |
|||
|
|||
byte[] encodeErrorEnvelope(string code, string message = null, object details = null); |
|||
} |
|||
|
|||
public class PlatformException : Exception { |
|||
public PlatformException(string code, |
|||
string message = null, |
|||
object details = null) { |
|||
D.assert(code != null); |
|||
this.code = code; |
|||
this.message = message; |
|||
this.details = details; |
|||
} |
|||
|
|||
public readonly string code; |
|||
|
|||
public readonly string message; |
|||
|
|||
public readonly object details; |
|||
|
|||
public override string ToString() => $"PlatformException({code}, {message}, {details})"; |
|||
} |
|||
|
|||
public class MissingPluginException : Exception { |
|||
public MissingPluginException(string message = null) { |
|||
this.message = message; |
|||
} |
|||
|
|||
public readonly string message; |
|||
|
|||
public override string ToString() => $"MissingPluginException({message})"; |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: ca3fe165f7b04b94813a0b9c115d08c2 |
|||
timeCreated: 1599790199 |
|
|||
using System; |
|||
using System.Collections; |
|||
using System.Globalization; |
|||
using System.Text; |
|||
using Unity.UIWidgets.foundation; |
|||
using Unity.UIWidgets.services; |
|||
|
|||
namespace Unity.UIWidgets.services { |
|||
public class BinaryCodec : MessageCodec<byte[]> { |
|||
public BinaryCodec() { |
|||
} |
|||
|
|||
public static readonly BinaryCodec instance = new BinaryCodec(); |
|||
|
|||
public byte[] decodeMessage(byte[] message) => message; |
|||
|
|||
public byte[] encodeMessage(byte[] message) => message; |
|||
} |
|||
|
|||
public class StringCodec : MessageCodec<string> { |
|||
public StringCodec() { |
|||
} |
|||
|
|||
public static readonly StringCodec instance = new StringCodec(); |
|||
|
|||
public string decodeMessage(byte[] message) { |
|||
if (message == null) |
|||
return null; |
|||
return Encoding.UTF8.GetString(message); |
|||
} |
|||
|
|||
public byte[] encodeMessage(string message) { |
|||
if (message == null) |
|||
return null; |
|||
return Encoding.UTF8.GetBytes(message); |
|||
} |
|||
} |
|||
|
|||
public class JSONMessageCodec : MessageCodec<object> { |
|||
public JSONMessageCodec() { |
|||
} |
|||
|
|||
public static readonly JSONMessageCodec instance = new JSONMessageCodec(); |
|||
|
|||
public byte[] encodeMessage(object message) { |
|||
if (message == null) |
|||
return null; |
|||
|
|||
var sb = new StringBuilder(); |
|||
_writeToStringBuilder(message, sb); |
|||
return StringCodec.instance.encodeMessage(sb.ToString()); |
|||
} |
|||
|
|||
public object decodeMessage(byte[] message) { |
|||
if (message == null) |
|||
return null; |
|||
|
|||
return _parseJson(StringCodec.instance.decodeMessage(message)); |
|||
} |
|||
|
|||
[ThreadStatic] static StringBuilder _escapeBuilder; |
|||
|
|||
static StringBuilder _getEscapeBuilder() { |
|||
return _escapeBuilder ?? (_escapeBuilder = new StringBuilder()); |
|||
} |
|||
|
|||
static string _escape(string aText) { |
|||
var sb = _getEscapeBuilder(); |
|||
sb.Length = 0; |
|||
if (sb.Capacity < aText.Length + aText.Length / 10) |
|||
sb.Capacity = aText.Length + aText.Length / 10; |
|||
|
|||
foreach (char c in aText) { |
|||
switch (c) { |
|||
case '\\': |
|||
sb.Append("\\\\"); |
|||
break; |
|||
case '\"': |
|||
sb.Append("\\\""); |
|||
break; |
|||
case '\n': |
|||
sb.Append("\\n"); |
|||
break; |
|||
case '\r': |
|||
sb.Append("\\r"); |
|||
break; |
|||
case '\t': |
|||
sb.Append("\\t"); |
|||
break; |
|||
case '\b': |
|||
sb.Append("\\b"); |
|||
break; |
|||
case '\f': |
|||
sb.Append("\\f"); |
|||
break; |
|||
default: |
|||
if (c < ' ') { |
|||
ushort val = c; |
|||
sb.Append("\\u").Append(val.ToString("X4")); |
|||
} |
|||
else |
|||
sb.Append(c); |
|||
|
|||
break; |
|||
} |
|||
} |
|||
|
|||
string result = sb.ToString(); |
|||
sb.Length = 0; |
|||
return result; |
|||
} |
|||
|
|||
static void _writeToStringBuilder(object obj, StringBuilder sb) { |
|||
if (obj is IDictionary dict) { |
|||
sb.Append('{'); |
|||
bool first = true; |
|||
foreach (DictionaryEntry k in dict) { |
|||
if (!first) |
|||
sb.Append(','); |
|||
first = false; |
|||
sb.Append('\"').Append(_escape(k.Key.ToString())).Append('\"'); |
|||
sb.Append(':'); |
|||
_writeToStringBuilder(k.Value, sb); |
|||
} |
|||
|
|||
sb.Append('}'); |
|||
} |
|||
else if (obj is IList list) { |
|||
sb.Append('['); |
|||
int count = list.Count; |
|||
for (int i = 0; i < count; i++) { |
|||
if (i > 0) |
|||
sb.Append(','); |
|||
_writeToStringBuilder(list[i], sb); |
|||
} |
|||
|
|||
sb.Append(']'); |
|||
} |
|||
else if (obj is string str) { |
|||
sb.Append('\"').Append(_escape(str)).Append('\"'); |
|||
} |
|||
else if (obj is double d) { |
|||
sb.Append(d.ToString(CultureInfo.InvariantCulture)); |
|||
} |
|||
else if (obj is float f) { |
|||
sb.Append(f.ToString(CultureInfo.InvariantCulture)); |
|||
} |
|||
else if (obj is int i) { |
|||
sb.Append(i.ToString(CultureInfo.InvariantCulture)); |
|||
} |
|||
else if (obj is long l) { |
|||
sb.Append(l.ToString(CultureInfo.InvariantCulture)); |
|||
} |
|||
else if (obj is bool b) { |
|||
sb.Append(b ? "true" : "false"); |
|||
} |
|||
else if (obj == null) { |
|||
sb.Append("null"); |
|||
} |
|||
else { |
|||
throw new Exception("Unsupported object: " + obj); |
|||
} |
|||
} |
|||
|
|||
static object _parseElement(string token, bool quoted) { |
|||
if (quoted) |
|||
return token; |
|||
|
|||
string tmp = token.ToLower(); |
|||
if (tmp == "false" || tmp == "true") |
|||
return tmp == "true"; |
|||
if (tmp == "null") |
|||
return null; |
|||
|
|||
if (double.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out var val)) |
|||
return val; |
|||
else |
|||
return token; |
|||
} |
|||
|
|||
static void _addElement(object ctx, string tokenName, object element) { |
|||
if (ctx is IDictionary dict) { |
|||
dict.Add(tokenName, element); |
|||
} |
|||
else if (ctx is IList list) { |
|||
D.assert(tokenName.isEmpty); |
|||
list.Add(element); |
|||
} |
|||
else { |
|||
D.assert(ctx == null); |
|||
} |
|||
} |
|||
|
|||
static object _parseJson(string jsonStr) { |
|||
Stack stack = new Stack(); |
|||
object ctx = null; |
|||
int i = 0; |
|||
StringBuilder token = new StringBuilder(); |
|||
string tokenName = ""; |
|||
bool quoteMode = false; |
|||
bool tokenIsQuoted = false; |
|||
while (i < jsonStr.Length) { |
|||
switch (jsonStr[i]) { |
|||
case '{': |
|||
if (quoteMode) { |
|||
token.Append(jsonStr[i]); |
|||
break; |
|||
} |
|||
|
|||
stack.Push(new Hashtable()); |
|||
_addElement(ctx, tokenName, stack.Peek()); |
|||
|
|||
tokenName = ""; |
|||
token.Length = 0; |
|||
ctx = stack.Peek(); |
|||
break; |
|||
|
|||
case '[': |
|||
if (quoteMode) { |
|||
token.Append(jsonStr[i]); |
|||
break; |
|||
} |
|||
|
|||
stack.Push(new ArrayList()); |
|||
_addElement(ctx, tokenName, stack.Peek()); |
|||
|
|||
tokenName = ""; |
|||
token.Length = 0; |
|||
ctx = stack.Peek(); |
|||
break; |
|||
|
|||
case '}': |
|||
case ']': |
|||
if (quoteMode) { |
|||
token.Append(jsonStr[i]); |
|||
break; |
|||
} |
|||
|
|||
if (stack.Count == 0) |
|||
throw new Exception("JSON Parse: Too many closing brackets"); |
|||
|
|||
stack.Pop(); |
|||
if (token.Length > 0 || tokenIsQuoted) |
|||
_addElement(ctx, tokenName, _parseElement(token.ToString(), tokenIsQuoted)); |
|||
|
|||
tokenIsQuoted = false; |
|||
tokenName = ""; |
|||
token.Length = 0; |
|||
|
|||
if (stack.Count > 0) |
|||
ctx = stack.Peek(); |
|||
break; |
|||
|
|||
case ':': |
|||
if (quoteMode) { |
|||
token.Append(jsonStr[i]); |
|||
break; |
|||
} |
|||
|
|||
tokenIsQuoted = false; |
|||
tokenName = token.ToString(); |
|||
token.Length = 0; |
|||
break; |
|||
|
|||
case '"': |
|||
quoteMode ^= true; |
|||
tokenIsQuoted |= quoteMode; |
|||
break; |
|||
|
|||
case ',': |
|||
if (quoteMode) { |
|||
token.Append(jsonStr[i]); |
|||
break; |
|||
} |
|||
|
|||
if (token.Length > 0 || tokenIsQuoted) |
|||
_addElement(ctx, tokenName, _parseElement(token.ToString(), tokenIsQuoted)); |
|||
|
|||
tokenIsQuoted = false; |
|||
tokenName = ""; |
|||
token.Length = 0; |
|||
break; |
|||
|
|||
case '\r': |
|||
case '\n': |
|||
break; |
|||
|
|||
case ' ': |
|||
case '\t': |
|||
if (quoteMode) |
|||
token.Append(jsonStr[i]); |
|||
break; |
|||
|
|||
case '\\': |
|||
++i; |
|||
if (quoteMode) { |
|||
char c = jsonStr[i]; |
|||
switch (c) { |
|||
case 't': |
|||
token.Append('\t'); |
|||
break; |
|||
case 'r': |
|||
token.Append('\r'); |
|||
break; |
|||
case 'n': |
|||
token.Append('\n'); |
|||
break; |
|||
case 'b': |
|||
token.Append('\b'); |
|||
break; |
|||
case 'f': |
|||
token.Append('\f'); |
|||
break; |
|||
case 'u': { |
|||
string s = jsonStr.Substring(i + 1, 4); |
|||
token.Append((char) int.Parse( |
|||
s, |
|||
System.Globalization.NumberStyles.AllowHexSpecifier)); |
|||
i += 4; |
|||
break; |
|||
} |
|||
default: |
|||
token.Append(c); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
break; |
|||
|
|||
default: |
|||
token.Append(jsonStr[i]); |
|||
break; |
|||
} |
|||
|
|||
++i; |
|||
} |
|||
|
|||
if (quoteMode) { |
|||
throw new Exception("JSON Parse: Quotation marks seems to be messed up."); |
|||
} |
|||
|
|||
if (ctx == null) |
|||
return _parseElement(token.ToString(), tokenIsQuoted); |
|||
return ctx; |
|||
} |
|||
} |
|||
} |
|||
|
|||
public class JSONMethodCodec : MethodCodec { |
|||
public JSONMethodCodec() { |
|||
} |
|||
|
|||
public static readonly JSONMethodCodec instance = new JSONMethodCodec(); |
|||
|
|||
public byte[] encodeMethodCall(MethodCall call) { |
|||
var obj = new Hashtable {{"method", call.method}, {"args", call.arguments}}; |
|||
return JSONMessageCodec.instance.encodeMessage(obj); |
|||
} |
|||
|
|||
public MethodCall decodeMethodCall(byte[] methodCall) { |
|||
object decoded = JSONMessageCodec.instance.decodeMessage(methodCall); |
|||
if (!(decoded is IDictionary dict)) |
|||
throw new Exception($"Expected method call JSONObject, got {decoded}"); |
|||
|
|||
object methodRaw = dict["method"]; |
|||
object arguments = dict["args"]; |
|||
|
|||
if (methodRaw is string method) |
|||
return new MethodCall(method, arguments); |
|||
|
|||
throw new Exception($"Invalid method call: {decoded}"); |
|||
} |
|||
|
|||
public object decodeEnvelope(byte[] envelope) { |
|||
object decoded = JSONMessageCodec.instance.decodeMessage(envelope); |
|||
if (!(decoded is IList list)) |
|||
throw new Exception($"Expected envelope JSONArray, got {decoded}"); |
|||
|
|||
if (list.Count == 1) |
|||
return list[0]; |
|||
if (list.Count == 3 |
|||
&& list[0] is string code |
|||
&& (list[1] == null || list[1] is string)) |
|||
throw new PlatformException( |
|||
code: code, |
|||
message: list[1] as string, |
|||
details: list[2] |
|||
); |
|||
throw new Exception($"Invalid envelope: {decoded}"); |
|||
} |
|||
|
|||
public byte[] encodeSuccessEnvelope(object result) { |
|||
var array = new ArrayList {result}; |
|||
return JSONMessageCodec.instance.encodeMessage(array); |
|||
} |
|||
|
|||
public byte[] encodeErrorEnvelope(string code, string message = null, object details = null) { |
|||
D.assert(code != null); |
|||
var array = new ArrayList {code, message, details}; |
|||
return JSONMessageCodec.instance.encodeMessage(array); |
|||
} |
|||
} |
|||
|
|||
public class StandardMessageCodec : MessageCodec<object> { |
|||
public StandardMessageCodec() { |
|||
} |
|||
|
|||
public static readonly StandardMessageCodec instance = new StandardMessageCodec(); |
|||
|
|||
const byte _valueNull = 0; |
|||
const byte _valueTrue = 1; |
|||
const byte _valueFalse = 2; |
|||
const byte _valueInt32 = 3; |
|||
const byte _valueInt64 = 4; |
|||
const byte _valueFloat32 = 6; |
|||
const byte _valueString = 7; |
|||
const byte _valueUint8List = 8; |
|||
const byte _valueInt32List = 9; |
|||
const byte _valueInt64List = 10; |
|||
const byte _valueFloat32List = 11; |
|||
const byte _valueList = 12; |
|||
const byte _valueMap = 13; |
|||
|
|||
public byte[] encodeMessage(object message) { |
|||
if (message == null) |
|||
return null; |
|||
WriteBuffer buffer = new WriteBuffer(); |
|||
writeValue(buffer, message); |
|||
return buffer.done(); |
|||
} |
|||
|
|||
public object decodeMessage(byte[] message) { |
|||
if (message == null) |
|||
return null; |
|||
ReadBuffer buffer = new ReadBuffer(message); |
|||
object result = readValue(buffer); |
|||
if (buffer.hasRemaining) |
|||
throw new Exception("Message corrupted"); |
|||
return result; |
|||
} |
|||
|
|||
public void writeValue(WriteBuffer buffer, object value) { |
|||
if (value == null) { |
|||
buffer.putUint8(_valueNull); |
|||
} |
|||
else if (value is bool b) { |
|||
buffer.putUint8(b ? _valueTrue : _valueFalse); |
|||
} |
|||
else if (value is float f) { |
|||
buffer.putUint8(_valueFloat32); |
|||
buffer.putFloat32(f); |
|||
} |
|||
else if (value is int i) { |
|||
buffer.putUint8(_valueInt32); |
|||
buffer.putInt32(i); |
|||
} |
|||
else if (value is long l) { |
|||
buffer.putUint8(_valueInt64); |
|||
buffer.putInt64(l); |
|||
} |
|||
else if (value is string s) { |
|||
buffer.putUint8(_valueString); |
|||
byte[] bytes = Encoding.UTF8.GetBytes(s); |
|||
writeSize(buffer, bytes.Length); |
|||
buffer.putUint8List(bytes); |
|||
} |
|||
else if (value is byte[] bytes) { |
|||
buffer.putUint8(_valueUint8List); |
|||
writeSize(buffer, bytes.Length); |
|||
buffer.putUint8List(bytes); |
|||
} |
|||
else if (value is int[] ints) { |
|||
buffer.putUint8(_valueInt32List); |
|||
writeSize(buffer, ints.Length); |
|||
buffer.putInt32List(ints); |
|||
} |
|||
else if (value is long[] longs) { |
|||
buffer.putUint8(_valueInt64List); |
|||
writeSize(buffer, longs.Length); |
|||
buffer.putInt64List(longs); |
|||
} |
|||
else if (value is float[] floats) { |
|||
buffer.putUint8(_valueFloat32List); |
|||
writeSize(buffer, floats.Length); |
|||
buffer.putFloat32List(floats); |
|||
} |
|||
else if (value is IList list) { |
|||
buffer.putUint8(_valueList); |
|||
writeSize(buffer, list.Count); |
|||
foreach (object item in list) { |
|||
writeValue(buffer, item); |
|||
} |
|||
} |
|||
else if (value is IDictionary dict) { |
|||
buffer.putUint8(_valueMap); |
|||
writeSize(buffer, dict.Count); |
|||
foreach (DictionaryEntry entry in dict) { |
|||
writeValue(buffer, entry.Key); |
|||
writeValue(buffer, entry.Value); |
|||
} |
|||
} |
|||
else { |
|||
throw new ArgumentException(value.ToString()); |
|||
} |
|||
} |
|||
|
|||
public object readValue(ReadBuffer buffer) { |
|||
if (!buffer.hasRemaining) |
|||
throw new Exception("Message corrupted"); |
|||
int type = buffer.getUint8(); |
|||
return readValueOfType(type, buffer); |
|||
} |
|||
|
|||
object readValueOfType(int type, ReadBuffer buffer) { |
|||
switch (type) { |
|||
case _valueNull: |
|||
return null; |
|||
case _valueTrue: |
|||
return true; |
|||
case _valueFalse: |
|||
return false; |
|||
case _valueInt32: |
|||
return buffer.getInt32(); |
|||
case _valueInt64: |
|||
return buffer.getInt64(); |
|||
case _valueFloat32: |
|||
return buffer.getFloat32(); |
|||
case _valueString: { |
|||
int length = (int) readSize(buffer); |
|||
return Encoding.UTF8.GetString(buffer.getUint8List(length)); |
|||
} |
|||
case _valueUint8List: { |
|||
int length = (int) readSize(buffer); |
|||
return buffer.getUint8List(length); |
|||
} |
|||
case _valueInt32List: { |
|||
int length = (int) readSize(buffer); |
|||
return buffer.getInt32List(length); |
|||
} |
|||
case _valueInt64List: { |
|||
int length = (int) readSize(buffer); |
|||
return buffer.getInt64List(length); |
|||
} |
|||
case _valueFloat32List: { |
|||
int length = (int) readSize(buffer); |
|||
return buffer.getFloat32List(length); |
|||
} |
|||
case _valueList: { |
|||
int length = (int) readSize(buffer); |
|||
var result = new ArrayList(length); |
|||
for (int i = 0; i < length; i++) |
|||
result.Add(readValue(buffer)); |
|||
return result; |
|||
} |
|||
case _valueMap: { |
|||
int length = (int) readSize(buffer); |
|||
Hashtable result = new Hashtable(); |
|||
for (int i = 0; i < length; i++) |
|||
result[readValue(buffer)] = readValue(buffer); |
|||
return result; |
|||
} |
|||
default: |
|||
throw new Exception("Message corrupted"); |
|||
} |
|||
} |
|||
|
|||
void writeSize(WriteBuffer buffer, long value) { |
|||
D.assert(0 <= value && value <= 0x7fffffff); |
|||
if (value < 254) { |
|||
buffer.putUint8((byte) value); |
|||
} |
|||
else if (value <= 0xffff) { |
|||
buffer.putUint8(254); |
|||
buffer.putUint16((ushort) value); |
|||
} |
|||
else { |
|||
buffer.putUint8(255); |
|||
buffer.putUint32((uint) value); |
|||
} |
|||
} |
|||
|
|||
long readSize(ReadBuffer buffer) { |
|||
int value = buffer.getUint8(); |
|||
switch (value) { |
|||
case 254: |
|||
return buffer.getUint16(); |
|||
case 255: |
|||
return buffer.getUint32(); |
|||
default: |
|||
return value; |
|||
} |
|||
} |
|||
} |
|||
|
|||
public class StandardMethodCodec : MethodCodec { |
|||
public StandardMethodCodec(StandardMessageCodec messageCodec = null) { |
|||
this.messageCodec = messageCodec ?? StandardMessageCodec.instance; |
|||
} |
|||
|
|||
readonly StandardMessageCodec messageCodec; |
|||
|
|||
public byte[] encodeMethodCall(MethodCall call) { |
|||
WriteBuffer buffer = new WriteBuffer(); |
|||
messageCodec.writeValue(buffer, call.method); |
|||
messageCodec.writeValue(buffer, call.arguments); |
|||
return buffer.done(); |
|||
} |
|||
|
|||
public MethodCall decodeMethodCall(byte[] methodCall) { |
|||
ReadBuffer buffer = new ReadBuffer(methodCall); |
|||
object methodRaw = messageCodec.readValue(buffer); |
|||
object arguments = messageCodec.readValue(buffer); |
|||
if (methodRaw is string method && !buffer.hasRemaining) |
|||
return new MethodCall(method, arguments); |
|||
else |
|||
throw new Exception("Invalid method call"); |
|||
} |
|||
|
|||
public byte[] encodeSuccessEnvelope(object result) { |
|||
WriteBuffer buffer = new WriteBuffer(); |
|||
buffer.putUint8(0); |
|||
messageCodec.writeValue(buffer, result); |
|||
return buffer.done(); |
|||
} |
|||
|
|||
public byte[] encodeErrorEnvelope(string code, string message = null, object details = null) { |
|||
WriteBuffer buffer = new WriteBuffer(); |
|||
buffer.putUint8(1); |
|||
messageCodec.writeValue(buffer, code); |
|||
messageCodec.writeValue(buffer, message); |
|||
messageCodec.writeValue(buffer, details); |
|||
return buffer.done(); |
|||
} |
|||
|
|||
public object decodeEnvelope(byte[] envelope) { |
|||
if (envelope.Length == 0) |
|||
throw new Exception("Expected envelope, got nothing"); |
|||
ReadBuffer buffer = new ReadBuffer(envelope); |
|||
if (buffer.getUint8() == 0) |
|||
return messageCodec.readValue(buffer); |
|||
object errorCodeRaw = messageCodec.readValue(buffer); |
|||
object errorMessage = messageCodec.readValue(buffer); |
|||
object errorDetails = messageCodec.readValue(buffer); |
|||
if (errorCodeRaw is string errorCode && (errorMessage == null || errorMessage is string) && |
|||
!buffer.hasRemaining) |
|||
throw new PlatformException(code: errorCode, message: errorMessage as string, details: errorDetails); |
|||
else |
|||
throw new Exception("Invalid envelope"); |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: eda58f2816f44397ac01597513782e10 |
|||
timeCreated: 1600152975 |
|
|||
#include "platform_message_response_dart.h"
|
|||
|
|||
#include <utility>
|
|||
|
|||
#include "common/task_runners.h"
|
|||
#include "flutter/fml/make_copyable.h"
|
|||
#include "lib/ui/window/window.h"
|
|||
|
|||
namespace uiwidgets { |
|||
|
|||
namespace { |
|||
|
|||
// Avoid copying the contents of messages beyond a certain size.
|
|||
const int kMessageCopyThreshold = 1000; |
|||
|
|||
void MessageDataFinalizer(void* isolate_callback_data, |
|||
Dart_WeakPersistentHandle handle, |
|||
void* peer) { |
|||
std::vector<uint8_t>* data = reinterpret_cast<std::vector<uint8_t>*>(peer); |
|||
delete data; |
|||
} |
|||
|
|||
Dart_Handle WrapByteData(std::vector<uint8_t> data) { |
|||
if (data.size() < kMessageCopyThreshold) { |
|||
return ToByteData(data); |
|||
} else { |
|||
std::vector<uint8_t>* heap_data = new std::vector<uint8_t>(std::move(data)); |
|||
return Dart_NewExternalTypedDataWithFinalizer( |
|||
Dart_TypedData_kByteData, heap_data->data(), heap_data->size(), |
|||
heap_data, heap_data->size(), MessageDataFinalizer); |
|||
} |
|||
} |
|||
|
|||
Dart_Handle WrapByteData(std::unique_ptr<fml::Mapping> mapping) { |
|||
std::vector<uint8_t> data(mapping->GetSize()); |
|||
memcpy(data.data(), mapping->GetMapping(), mapping->GetSize()); |
|||
return WrapByteData(std::move(data)); |
|||
} |
|||
|
|||
} // anonymous namespace
|
|||
|
|||
PlatformMessageResponseDart::PlatformMessageResponseDart( |
|||
tonic::DartPersistentValue callback, |
|||
fml::RefPtr<fml::TaskRunner> ui_task_runner) |
|||
: callback_(std::move(callback)), |
|||
ui_task_runner_(std::move(ui_task_runner)) {} |
|||
|
|||
PlatformMessageResponseDart::~PlatformMessageResponseDart() { |
|||
if (!callback_.is_empty()) { |
|||
ui_task_runner_->PostTask(fml::MakeCopyable( |
|||
[callback = std::move(callback_)]() mutable { callback.Clear(); })); |
|||
} |
|||
} |
|||
|
|||
void PlatformMessageResponseDart::Complete(std::unique_ptr<fml::Mapping> data) { |
|||
if (callback_.is_empty()) |
|||
return; |
|||
FML_DCHECK(!is_complete_); |
|||
is_complete_ = true; |
|||
ui_task_runner_->PostTask(fml::MakeCopyable( |
|||
[callback = std::move(callback_), data = std::move(data)]() mutable { |
|||
std::shared_ptr<tonic::DartState> dart_state = |
|||
callback.dart_state().lock(); |
|||
if (!dart_state) |
|||
return; |
|||
tonic::DartState::Scope scope(dart_state); |
|||
|
|||
Dart_Handle byte_buffer = WrapByteData(std::move(data)); |
|||
tonic::DartInvoke(callback.Release(), {byte_buffer}); |
|||
})); |
|||
} |
|||
|
|||
void PlatformMessageResponseDart::CompleteEmpty() { |
|||
if (callback_.is_empty()) |
|||
return; |
|||
FML_DCHECK(!is_complete_); |
|||
is_complete_ = true; |
|||
ui_task_runner_->PostTask( |
|||
fml::MakeCopyable([callback = std::move(callback_)]() mutable { |
|||
std::shared_ptr<tonic::DartState> dart_state = |
|||
callback.dart_state().lock(); |
|||
if (!dart_state) |
|||
return; |
|||
tonic::DartState::Scope scope(dart_state); |
|||
tonic::DartInvoke(callback.Release(), {Dart_Null()}); |
|||
})); |
|||
} |
|||
|
|||
} // namespace flutter
|
|
|||
#pragma once |
|||
|
|||
#include "flutter/fml/message_loop.h" |
|||
#include "platform_message_response.h" |
|||
|
|||
namespace uiwidgets { |
|||
|
|||
class PlatformMessageResponseDart : public PlatformMessageResponse { |
|||
FML_FRIEND_MAKE_REF_COUNTED(PlatformMessageResponseDart); |
|||
|
|||
public: |
|||
// Callable on any thread. |
|||
void Complete(std::unique_ptr<fml::Mapping> data) override; |
|||
void CompleteEmpty() override; |
|||
|
|||
protected: |
|||
explicit PlatformMessageResponseDart( |
|||
tonic::DartPersistentValue callback, |
|||
fml::RefPtr<fml::TaskRunner> ui_task_runner); |
|||
~PlatformMessageResponseDart() override; |
|||
|
|||
tonic::DartPersistentValue callback_; |
|||
fml::RefPtr<fml::TaskRunner> ui_task_runner_; |
|||
}; |
|||
|
|||
} // namespace uiwidgets |
撰写
预览
正在加载...
取消
保存
Reference in new issue