Unverified Commit 921c8030 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] support hot reload of font assets (#109091)

parent f9391522
...@@ -20,6 +20,8 @@ import 'compile.dart'; ...@@ -20,6 +20,8 @@ import 'compile.dart';
import 'convert.dart' show base64, utf8; import 'convert.dart' show base64, utf8;
import 'vmservice.dart'; import 'vmservice.dart';
const String _kFontManifest = 'FontManifest.json';
class DevFSConfig { class DevFSConfig {
/// Should DevFS assume that symlink targets are stable? /// Should DevFS assume that symlink targets are stable?
bool cacheSymlinks = false; bool cacheSymlinks = false;
...@@ -485,6 +487,9 @@ class DevFS { ...@@ -485,6 +487,9 @@ class DevFS {
// A flag to indicate whether we have called `setAssetDirectory` on the target device. // A flag to indicate whether we have called `setAssetDirectory` on the target device.
bool hasSetAssetDirectory = false; bool hasSetAssetDirectory = false;
/// Whether the font manifest was uploaded during [update].
bool didUpdateFontManifest = false;
List<Uri> sources = <Uri>[]; List<Uri> sources = <Uri>[];
DateTime? lastCompiled; DateTime? lastCompiled;
DateTime? _previousCompiled; DateTime? _previousCompiled;
...@@ -589,6 +594,7 @@ class DevFS { ...@@ -589,6 +594,7 @@ class DevFS {
assert(trackWidgetCreation != null); assert(trackWidgetCreation != null);
assert(generator != null); assert(generator != null);
final DateTime candidateCompileTime = DateTime.now(); final DateTime candidateCompileTime = DateTime.now();
didUpdateFontManifest = false;
lastPackageConfig = packageConfig; lastPackageConfig = packageConfig;
_widgetCacheOutputFile = _fileSystem.file('$dillOutputPath.incremental.dill.widget_cache'); _widgetCacheOutputFile = _fileSystem.file('$dillOutputPath.incremental.dill.widget_cache');
...@@ -644,6 +650,11 @@ class DevFS { ...@@ -644,6 +650,11 @@ class DevFS {
if (deviceUri.path.startsWith(assetBuildDirPrefix)) { if (deviceUri.path.startsWith(assetBuildDirPrefix)) {
archivePath = deviceUri.path.substring(assetBuildDirPrefix.length); archivePath = deviceUri.path.substring(assetBuildDirPrefix.length);
} }
// If the font manifest is updated, mark this as true so the hot runner
// can invoke a service extension to force the engine to reload fonts.
if (archivePath == _kFontManifest) {
didUpdateFontManifest = true;
}
if (bundle.entryKinds[archivePath] == AssetKind.shader) { if (bundle.entryKinds[archivePath] == AssetKind.shader) {
final Future<DevFSContent?> pending = shaderCompiler.recompileShader(content); final Future<DevFSContent?> pending = shaderCompiler.recompileShader(content);
......
...@@ -677,6 +677,9 @@ class WebDevFS implements DevFS { ...@@ -677,6 +677,9 @@ class WebDevFS implements DevFS {
@override @override
bool hasSetAssetDirectory = false; bool hasSetAssetDirectory = false;
@override
bool didUpdateFontManifest = false;
Future<DebugConnection>? _cachedExtensionFuture; Future<DebugConnection>? _cachedExtensionFuture;
StreamSubscription<void>? _connectedApps; StreamSubscription<void>? _connectedApps;
......
...@@ -1032,7 +1032,7 @@ class HotRunner extends ResidentRunner { ...@@ -1032,7 +1032,7 @@ class HotRunner extends ResidentRunner {
@visibleForTesting @visibleForTesting
Future<void> evictDirtyAssets() async { Future<void> evictDirtyAssets() async {
final List<Future<Map<String, dynamic>?>> futures = <Future<Map<String, dynamic>>>[]; final List<Future<void>> futures = <Future<void>>[];
for (final FlutterDevice? device in flutterDevices) { for (final FlutterDevice? device in flutterDevices) {
if (device!.devFS!.assetPathsToEvict.isEmpty && device.devFS!.shaderPathsToEvict.isEmpty) { if (device!.devFS!.assetPathsToEvict.isEmpty && device.devFS!.shaderPathsToEvict.isEmpty) {
continue; continue;
...@@ -1060,6 +1060,14 @@ class HotRunner extends ResidentRunner { ...@@ -1060,6 +1060,14 @@ class HotRunner extends ResidentRunner {
globals.printError('Application isolate not found for $device'); globals.printError('Application isolate not found for $device');
continue; continue;
} }
if (device.devFS!.didUpdateFontManifest) {
futures.add(device.vmService!.reloadAssetFonts(
isolateId: views.first.uiIsolate!.id!,
viewId: views.first.id,
));
}
for (final String assetPath in device.devFS!.assetPathsToEvict) { for (final String assetPath in device.devFS!.assetPathsToEvict) {
futures.add( futures.add(
device.vmService! device.vmService!
...@@ -1081,7 +1089,7 @@ class HotRunner extends ResidentRunner { ...@@ -1081,7 +1089,7 @@ class HotRunner extends ResidentRunner {
device.devFS!.assetPathsToEvict.clear(); device.devFS!.assetPathsToEvict.clear();
device.devFS!.shaderPathsToEvict.clear(); device.devFS!.shaderPathsToEvict.clear();
} }
await Future.wait<Map<String, Object?>?>(futures); await Future.wait<void>(futures);
} }
@override @override
......
...@@ -24,6 +24,7 @@ const String kListViewsMethod = '_flutter.listViews'; ...@@ -24,6 +24,7 @@ const String kListViewsMethod = '_flutter.listViews';
const String kScreenshotSkpMethod = '_flutter.screenshotSkp'; const String kScreenshotSkpMethod = '_flutter.screenshotSkp';
const String kScreenshotMethod = '_flutter.screenshot'; const String kScreenshotMethod = '_flutter.screenshot';
const String kRenderFrameWithRasterStatsMethod = '_flutter.renderFrameWithRasterStats'; const String kRenderFrameWithRasterStatsMethod = '_flutter.renderFrameWithRasterStats';
const String kReloadAssetFonts = '_flutter.reloadAssetFonts';
/// The error response code from an unrecoverable compilation failure. /// The error response code from an unrecoverable compilation failure.
const int kIsolateReloadBarred = 1005; const int kIsolateReloadBarred = 1005;
...@@ -880,6 +881,20 @@ class FlutterVmService { ...@@ -880,6 +881,20 @@ class FlutterVmService {
} }
} }
/// Tell the provided flutter view that the font manifest has been updated
/// and asset fonts should be reloaded.
Future<void> reloadAssetFonts({
required String isolateId,
required String viewId,
}) async {
await callMethodWrapper(
kReloadAssetFonts,
isolateId: isolateId, args: <String, Object?>{
'viewId': viewId,
},
);
}
/// Waits for a signal from the VM service that [extensionName] is registered. /// Waits for a signal from the VM service that [extensionName] is registered.
/// ///
/// Looks at the list of loaded extensions for first Flutter view, as well as /// Looks at the list of loaded extensions for first Flutter view, as well as
......
...@@ -643,6 +643,57 @@ void main() { ...@@ -643,6 +643,57 @@ void main() {
FileSystem: () => fileSystem, FileSystem: () => fileSystem,
ProcessManager: () => processManager, ProcessManager: () => processManager,
}); });
testUsingContext('DevFS tracks when FontManifest is updated', () async {
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
requests: <VmServiceExpectation>[createDevFSRequest],
httpAddress: Uri.parse('http://localhost'),
);
final BufferLogger logger = BufferLogger.test();
final DevFS devFS = DevFS(
fakeVmServiceHost.vmService,
'test',
fileSystem.currentDirectory,
fileSystem: fileSystem,
logger: logger,
osUtils: FakeOperatingSystemUtils(),
httpClient: FakeHttpClient.any(),
);
await devFS.create();
expect(devFS.didUpdateFontManifest, false);
final FakeResidentCompiler residentCompiler = FakeResidentCompiler()
..onRecompile = (Uri mainUri, List<Uri>? invalidatedFiles) async {
fileSystem.file('lib/foo.dill')
..createSync(recursive: true)
..writeAsBytesSync(<int>[1, 2, 3, 4, 5]);
return const CompilerOutput('lib/foo.dill', 0, <Uri>[]);
};
final FakeBundle bundle = FakeBundle()
..entries['FontManifest.json'] = DevFSByteContent(<int>[1, 2, 3, 4]);
final UpdateFSReport report = await devFS.update(
mainUri: Uri.parse('lib/main.dart'),
generator: residentCompiler,
dillOutputPath: 'lib/foo.dill',
pathToReload: 'lib/foo.txt.dill',
trackWidgetCreation: false,
invalidatedFiles: <Uri>[],
packageConfig: PackageConfig.empty,
shaderCompiler: const FakeShaderCompiler(),
bundle: bundle,
);
expect(report.success, true);
expect(devFS.shaderPathsToEvict, <String>{});
expect(devFS.assetPathsToEvict, <String>{'FontManifest.json'});
expect(devFS.didUpdateFontManifest, true);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
}); });
} }
......
...@@ -2633,6 +2633,9 @@ class FakeDevFS extends Fake implements DevFS { ...@@ -2633,6 +2633,9 @@ class FakeDevFS extends Fake implements DevFS {
@override @override
Set<String> shaderPathsToEvict = <String>{}; Set<String> shaderPathsToEvict = <String>{};
@override
bool didUpdateFontManifest = false;
UpdateFSReport nextUpdateReport = UpdateFSReport(success: true); UpdateFSReport nextUpdateReport = UpdateFSReport(success: true);
@override @override
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment