blob: 0dfc4baccf2efecaf7819813cf695ba2b58335fc [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:meta/meta.dart';
import '../util.dart';
import 'embedded_views.dart';
import 'surface.dart';
/// Caches surfaces used to overlay platform views.
class SurfaceFactory {
/// The cache singleton.
static final SurfaceFactory instance =
SurfaceFactory(HtmlViewEmbedder.maximumOverlaySurfaces);
SurfaceFactory(this.maximumSurfaces)
: assert(maximumSurfaces >= 2,
'The maximum number of surfaces must be at least 2');
/// The base surface to paint on. This is the default surface which will be
/// painted to. If there are no platform views, then this surface will receive
/// all painting commands.
final Surface baseSurface = Surface();
/// The shared backup surface
final Surface backupSurface = Surface();
/// The maximum number of surfaces which can be live at once.
final int maximumSurfaces;
/// Surfaces created by this factory which are currently in use.
final List<Surface> _liveSurfaces = <Surface>[];
/// Surfaces created by this factory which are no longer in use. These can be
/// reused.
final List<Surface> _cache = <Surface>[];
/// The number of surfaces which have been created by this factory.
int get _surfaceCount => _liveSurfaces.length + _cache.length + 2;
/// The number of surfaces created by this factory. Used for testing.
@visibleForTesting
int get debugSurfaceCount => _surfaceCount;
/// Returns the number of cached surfaces.
///
/// Useful in tests.
int get debugCacheSize => _cache.length;
/// Whether or not we have already emitted a warning about creating too many
/// surfaces.
bool _warnedAboutTooManySurfaces = false;
/// Gets a [Surface] which is ready to paint to.
///
/// If there are available surfaces in the cache, then this will return one of
/// them. If this factory hasn't yet created [maximumSurfaces] surfaces, then a
/// new one will be created. If this factory has already created [maximumSurfaces]
/// surfaces, then this will return a backup surface which will be returned by
/// all subsequent calls to [getSurface] until some surfaces have been
/// released with [releaseSurface].
Surface getSurface() {
if (_cache.isNotEmpty) {
final Surface surface = _cache.removeLast();
_liveSurfaces.add(surface);
return surface;
} else if (debugSurfaceCount < maximumSurfaces) {
final Surface surface = Surface();
_liveSurfaces.add(surface);
return surface;
} else {
if (!_warnedAboutTooManySurfaces) {
_warnedAboutTooManySurfaces = true;
printWarning('Flutter was unable to create enough overlay surfaces. '
'This is usually caused by too many platform views being '
'displayed at once. '
'You may experience incorrect rendering.');
}
return backupSurface;
}
}
/// Signals that a surface is no longer being used. It can be reused.
void releaseSurface(Surface surface) {
assert(surface != baseSurface, 'Attempting to release the base surface');
if (surface == backupSurface) {
// If it's the backup surface, just remove it from the DOM.
surface.htmlElement.remove();
return;
}
assert(
_liveSurfaces.contains(surface),
'Attempting to release a Surface which '
'was not created by this factory');
surface.htmlElement.remove();
_liveSurfaces.remove(surface);
_cache.add(surface);
}
/// Returns [true] if [surface] is currently being used to paint content.
///
/// The base surface and backup surface always count as live.
///
/// If a surface is not live, then it must be in the cache and ready to be
/// reused.
bool isLive(Surface surface) {
if (surface == baseSurface ||
surface == backupSurface ||
_liveSurfaces.contains(surface)) {
return true;
}
assert(_cache.contains(surface));
return false;
}
/// Dispose all surfaces created by this factory. Used in tests.
void debugClear() {
for (final Surface surface in _cache) {
surface.dispose();
}
for (final Surface surface in _liveSurfaces) {
surface.dispose();
}
_liveSurfaces.clear();
_cache.clear();
}
}