Preventing errors caused by the premature use of size property on game (#659)

This commit is contained in:
Erick
2021-02-15 19:07:58 -03:00
committed by GitHub
parent 12cafb1da3
commit 0d4df1acc2
8 changed files with 46 additions and 16 deletions

View File

@ -13,6 +13,7 @@
- Added `Color` extensions - Added `Color` extensions
- Change RaisedButton to ElevatedButton in timer example - Change RaisedButton to ElevatedButton in timer example
- Overhaul the draggables api to fix issues relating to local vs global positions - Overhaul the draggables api to fix issues relating to local vs global positions
- Preventing errors caused by the premature use of size property on game
## 1.0.0-rc6 ## 1.0.0-rc6
- Use `Offset` type directly in `JoystickAction.update` calculations - Use `Offset` type directly in `JoystickAction.update` calculations

View File

@ -63,7 +63,13 @@ class BaseGame extends Game with FPSCounter {
/// Prepares and registers a component to be added on the next game tick /// Prepares and registers a component to be added on the next game tick
/// ///
/// This methods is an async operation since it await the `onLoad` method of the component. Nevertheless, this method only need to be waited to finish if by some reason, your logic needs to be sure that the component has finished loading, otherwise, this method can be called without waiting for it to finish as the BaseGame already handle the loading of the component. /// This methods is an async operation since it await the `onLoad` method of the component. Nevertheless, this method only need to be waited to finish if by some reason, your logic needs to be sure that the component has finished loading, otherwise, this method can be called without waiting for it to finish as the BaseGame already handle the loading of the component.
///
/// *Note:* Do not add components on the game constructor. This method can only be called after the game already has its layout set, this can be verified by the [hasLayout] property, to add components upon a game initialization, the [onLoad] method can be used instead.
Future<void> add(Component c) async { Future<void> add(Component c) async {
assert(
hasLayout,
'"add" called before the game is ready. Did you try to access it on the Game constructor? Use the "onLoad" method instead.',
);
prepare(c); prepare(c);
final loadFuture = c.onLoad(); final loadFuture = c.onLoad();
@ -90,7 +96,7 @@ class BaseGame extends Game with FPSCounter {
/// This implementation of render basically calls [renderComponent] for every component, making sure the canvas is reset for each one. /// This implementation of render basically calls [renderComponent] for every component, making sure the canvas is reset for each one.
/// ///
/// You can override it further to add more custom behaviour. /// You can override it further to add more custom behavior.
/// Beware of however you are rendering components if not using this; you must be careful to save and restore the canvas to avoid components messing up with each other. /// Beware of however you are rendering components if not using this; you must be careful to save and restore the canvas to avoid components messing up with each other.
@override @override
@mustCallSuper @mustCallSuper
@ -116,7 +122,7 @@ class BaseGame extends Game with FPSCounter {
/// This implementation of update updates every component in the list. /// This implementation of update updates every component in the list.
/// ///
/// It also actually adds the components that were added by the [addLater] method, and remove those that are marked for destruction via the [Component.shouldRemove] method. /// It also actually adds the components that were added by the [addLater] method, and remove those that are marked for destruction via the [Component.shouldRemove] method.
/// You can override it further to add more custom behaviour. /// You can override it further to add more custom behavior.
@override @override
@mustCallSuper @mustCallSuper
void update(double t) { void update(double t) {
@ -139,7 +145,7 @@ class BaseGame extends Game with FPSCounter {
/// This implementation of resize passes the resize call along to every component in the list, enabling each one to make their decisions as how to handle the resize. /// This implementation of resize passes the resize call along to every component in the list, enabling each one to make their decisions as how to handle the resize.
/// ///
/// It also updates the [size] field of the class to be used by later added components and other methods. /// It also updates the [size] field of the class to be used by later added components and other methods.
/// You can override it further to add more custom behaviour, but you should seriously consider calling the super implementation as well. /// You can override it further to add more custom behavior, but you should seriously consider calling the super implementation as well.
@override @override
@mustCallSuper @mustCallSuper
void onResize(Vector2 size) { void onResize(Vector2 size) {

View File

@ -30,11 +30,28 @@ abstract class Game {
/// Currently attached build context. Can be null if not attached. /// Currently attached build context. Can be null if not attached.
BuildContext get buildContext => _gameRenderBox?.buildContext; BuildContext get buildContext => _gameRenderBox?.buildContext;
/// Current game viewport size, updated every resize via the [resize] method hook /// Whether the game widget was attached to the Flutter tree.
final Vector2 size = Vector2.zero();
bool get isAttached => buildContext != null; bool get isAttached => buildContext != null;
/// Current size of the game as provided by the framework; it will be null if layout has not been computed yet.
///
/// Use [size] and [hasLayout] for safe access.
Vector2 _size;
/// Current game viewport size, updated every resize via the [resize] method hook
Vector2 get size {
assert(
hasLayout,
'"size" is not ready yet. Did you try to access it on the Game constructor? Use the "onLoad" method instead.',
);
return _size;
}
/// Indicates if the this game instance had its layout layed into the GameWidget
/// Only this is true, the game is ready to have its size used or in the case
/// of a BaseGame, to receive components.
bool get hasLayout => _size != null;
/// Returns the game background color. /// Returns the game background color.
/// By default it will return a black color. /// By default it will return a black color.
/// It cannot be changed at runtime, because the game widget does not get rebuild when this value changes. /// It cannot be changed at runtime, because the game widget does not get rebuild when this value changes.
@ -53,7 +70,7 @@ abstract class Game {
/// The default implementation just sets the new size on the size field /// The default implementation just sets the new size on the size field
@mustCallSuper @mustCallSuper
void onResize(Vector2 size) { void onResize(Vector2 size) {
this.size.setFrom(size); _size = (_size ?? Vector2.zero())..setFrom(size);
} }
/// This is the lifecycle state change hook; every time the game is resumed, paused or suspended, this is called. /// This is the lifecycle state change hook; every time the game is resumed, paused or suspended, this is called.
@ -96,6 +113,7 @@ abstract class Game {
/// Should not be called manually. /// Should not be called manually.
void detach() { void detach() {
_gameRenderBox = null; _gameRenderBox = null;
_size = null;
onDetach(); onDetach();
} }

View File

@ -67,7 +67,7 @@ void main() {
final MyGame game = MyGame(); final MyGame game = MyGame();
final MyComponent component = MyComponent(); final MyComponent component = MyComponent();
game.size.setFrom(size); game.onResize(size);
game.add(component); game.add(component);
// runs a cycle to add the component // runs a cycle to add the component
game.update(0.1); game.update(0.1);
@ -80,7 +80,7 @@ void main() {
final MyGame game = MyGame(); final MyGame game = MyGame();
final MyAsyncComponent component = MyAsyncComponent(); final MyAsyncComponent component = MyAsyncComponent();
game.size.setFrom(size); game.onResize(size);
await game.add(component); await game.add(component);
// runs a cycle to add the component // runs a cycle to add the component
game.update(0.1); game.update(0.1);
@ -95,7 +95,7 @@ void main() {
final MyGame game = MyGame(); final MyGame game = MyGame();
final MyComponent component = MyComponent(); final MyComponent component = MyComponent();
game.size.setFrom(size); game.onResize(size);
game.add(component); game.add(component);
expect(component.gameSize, size); expect(component.gameSize, size);
@ -106,7 +106,7 @@ void main() {
final MyGame game = MyGame(); final MyGame game = MyGame();
final MyComponent component = MyComponent(); final MyComponent component = MyComponent();
game.size.setFrom(size); game.onResize(size);
game.add(component); game.add(component);
// The component is not added to the component list until an update has been performed // The component is not added to the component list until an update has been performed
game.update(0.0); game.update(0.0);
@ -119,7 +119,7 @@ void main() {
final MyGame game = MyGame(); final MyGame game = MyGame();
final MyComponent component = MyComponent(); final MyComponent component = MyComponent();
game.size.setFrom(size); game.onResize(size);
game.add(component); game.add(component);
// The component is not added to the component list until an update has been performed // The component is not added to the component list until an update has been performed
game.update(0.0); game.update(0.0);
@ -132,7 +132,7 @@ void main() {
final MyGame game = MyGame(); final MyGame game = MyGame();
final MyComponent component = MyComponent(); final MyComponent component = MyComponent();
game.size.setFrom(size); game.onResize(size);
game.add(component); game.add(component);
GameRenderBox renderBox; GameRenderBox renderBox;
tester.pumpWidget( tester.pumpWidget(
@ -158,7 +158,7 @@ void main() {
final MyGame game = MyGame(); final MyGame game = MyGame();
final MyComponent component = MyComponent(); final MyComponent component = MyComponent();
game.size.setFrom(size); game.onResize(size);
game.add(component); game.add(component);
// The component is not added to the component list until an update has been performed // The component is not added to the component list until an update has been performed
game.update(0.0); game.update(0.0);

View File

@ -87,7 +87,7 @@ void main() {
final MyTap child = MyTap(); final MyTap child = MyTap();
final MyComposed wrapper = MyComposed(); final MyComposed wrapper = MyComposed();
game.size.setFrom(size); game.onResize(size);
game.add(wrapper); game.add(wrapper);
wrapper.addChild(child); wrapper.addChild(child);
game.update(0.0); game.update(0.0);
@ -106,7 +106,7 @@ void main() {
..position = Vector2.all(100) ..position = Vector2.all(100)
..size = Vector2.all(300); ..size = Vector2.all(300);
game.size.setFrom(size); game.onResize(size);
game.add(wrapper); game.add(wrapper);
wrapper.addChild(child); wrapper.addChild(child);
game.update(0.0); game.update(0.0);
@ -119,6 +119,7 @@ void main() {
test('updates and renders children', () { test('updates and renders children', () {
final MyGame game = MyGame(); final MyGame game = MyGame();
game.onResize(Vector2.all(100));
final MyTap child = MyTap(); final MyTap child = MyTap();
final MyComposed wrapper = MyComposed(); final MyComposed wrapper = MyComposed();

View File

@ -20,6 +20,7 @@ void main() {
test('simple test', () { test('simple test', () {
final MyComponent c = MyComponent(); final MyComponent c = MyComponent();
final MyGame game = MyGame(); final MyGame game = MyGame();
game.onResize(Vector2.all(200));
game.add(c); game.add(c);
c.foo(); c.foo();
expect(game.calledFoo, true); expect(game.calledFoo, true);

View File

@ -33,6 +33,7 @@ void main() {
test('game calls resize after added', () { test('game calls resize after added', () {
final MyComponent a = MyComponent('a'); final MyComponent a = MyComponent('a');
final MyGame game = MyGame(); final MyGame game = MyGame();
game.onResize(Vector2.all(10));
game.add(a); game.add(a);
game.onResize(size); game.onResize(size);
expect(a.gameSize, size); expect(a.gameSize, size);
@ -40,6 +41,7 @@ void main() {
test("game calls doesn't change component size", () { test("game calls doesn't change component size", () {
final MyComponent a = MyComponent('a'); final MyComponent a = MyComponent('a');
final MyGame game = MyGame(); final MyGame game = MyGame();
game.onResize(Vector2.all(10));
game.add(a); game.add(a);
game.onResize(size); game.onResize(size);
expect(a.size, isNot(size)); expect(a.size, isNot(size));

View File

@ -28,6 +28,7 @@ void effectTest(
final Callback callback = Callback(); final Callback callback = Callback();
effect.onComplete = callback.call; effect.onComplete = callback.call;
final BaseGame game = BaseGame(); final BaseGame game = BaseGame();
game.onResize(Vector2.all(200));
game.add(component); game.add(component);
component.addEffect(effect); component.addEffect(effect);
final double duration = effect.iterationTime; final double duration = effect.iterationTime;