Question about Isolate in Flutter and match-3 game implementation
bluenote
Posted on June 13, 2023
I have been making match-3 game for implementing tile matching detection code. I have tried three kind of variations that do the work. The following error occurs each time known solution is tried:
ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Invalid argument(s): Illegal argument in isolate message: object is unsendable - Library:'package:flutter/src/scheduler/ticker.dart' Class: TickerFuture (see restrictions listed at SendPort.send()
documentation for more information)
I have experienced as if type mistake which I didn't do, when I copy and paste the same code I wrote then syntax error does not continue. But the error the above is occurring any codes as solution, changing many things to code and reverted back, the file location error occurred changed to scheduler/ticker.dart.
I know the file is resposible for animation work, but there is no problem at all.
I am worrying I had to be like Shimomura to resolve this problem, the following code would contain type mistake while I write this asking too.
The following is code:
import 'dart:async';
import 'dart:isolate';
import 'dart:math';
import 'package:flutter/material.dart';
// ignore: must_be_immutable
class Tile extends StatelessWidget {
List mBitmap = [
Image.asset('assets/images/games/match-3/tile0.jpg'),
Image.asset('assets/images/games/match-3/tile1.jpg'),
Image.asset('assets/images/games/match-3/tile2.jpg'),
Image.asset('assets/images/games/match-3/tile3.jpg'),
];
var mBitmapDestroy = Image.asset('assets/images/games/match-3/heart.jpg');
late int value;
late int row;
late int col;
late double mTileWidth;
late double mTileHeight;
Tile({required this.value, required this.row, required this.col, Key? key})
: super(key: key);
get image {
switch (value) {
case 0:
return mBitmap[0];
case 1:
return mBitmap[1];
case 2:
return mBitmap[2];
case 3:
return mBitmap[3];
case 4:
return mBitmapDestroy;
}
}
get getRow => row;
get getCol => col;
get getValue => value;
setRow(int row) {
this.row = row;
}
setCol(int col) {
this.col = col;
}
setValue(int value) {
this.value = value;
}
@override
Widget build(BuildContext context) {
mTileWidth = MediaQuery.of(context).size.width;
mTileHeight = mTileWidth;
return SizedBox(
width: mTileWidth,
height: mTileHeight,
child: image,
);
}
}
class TileMatchingGamePage extends StatelessWidget {
const TileMatchingGamePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) => const TileMatchingGameStatePage();
}
class TileMatchingGameStatePage extends StatefulWidget {
const TileMatchingGameStatePage({Key? key}) : super(key: key);
@override
State<TileMatchingGameStatePage> createState() =>
_TileMatchingGameStatePage();
}
class _TileMatchingGameStatePage extends State<TileMatchingGameStatePage>
with SingleTickerProviderStateMixin {
late List<List> tiles;
late List<List> toRemove;
late List<List<Tween<Offset>>> _offsetTweens;
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 400),
);
}
@override
void dispose() {
super.dispose();
_controller.dispose();
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
tiles = List.generate(
7,
(i) => List.generate(
7,
(j) => Tile(value: Random().nextInt(4), row: i, col: j),
),
);
toRemove = List.generate(
7,
(i) => List.generate(
7,
(j) => false,
),
);
_offsetTweens = List.generate(
7,
(i) => List.generate(
7,
(j) => Tween<Offset>(begin: Offset.zero, end: Offset.zero),
),
);
}
int mScore = 0;
int mCombo = 0;
bool checkAdjacentTiles() {
bool bFound = false;
// 모든 칸 리셋
for (int x = 0; x < 7; x++) {
for (int y = 0; y < 7; y++) {
toRemove[x][y] = false;
}
}
// 전체 순회, 3연속 찾기
for (int x = 0; x < 7; x++) {
for (int y = 0; y < 7; y++) {
int value = tiles[x][y].getValue;
// 수평 3개가 같으면 제거 대상
if (x > 0 && x < 7 - 1) {
if ((tiles[x - 1][y].getValue == value) &&
(tiles[x + 1][y].getValue == value)) {
toRemove[x - 1][y] = true;
toRemove[x][y] = true;
toRemove[x + 1][y] = true;
bFound = true;
}
}
// 수직 3개가 같으면 제거 대상
if (y > 0 && y < 7 - 1) {
if ((tiles[x][y - 1].getValue == value) &&
(tiles[x][y + 1].getValue == value)) {
toRemove[x][y - 1] = true;
toRemove[x][y] = true;
toRemove[x][y + 1] = true;
bFound = true;
}
}
}
}
mCombo++;
return bFound;
}
void removeTiles(_) {
setState(() {
int count = 0;
for (int x = 0; x < 7; x++) {
for (int y = 0; y < 7; y++) {
if (toRemove[x][y]) {
tiles[x][y] = Tile(value: 4, row: x, col: y);
count++;
}
}
}
mScore += (count * mCombo);
bool bFound = false;
for (int y = 7 - 1; y >= 0; y--) {
bFound = false;
for (int x = 0; x < 7; x++) {
if (toRemove[x][y]) {
bFound = true;
for (int ty = y; ty > 0; ty--) {
tiles[x][ty].setValue(tiles[x][ty - 1].getValue);
}
tiles[x][0].setValue(4);
}
}
if (bFound) y++;
}
});
Future.delayed(const Duration(milliseconds: 600));
setState(() {
for (int x = 0; x < 7; x++) {
for (int y = 0; y < 7; y++) {
if (tiles[x][y].getValue == 4) {
tiles[x][y] = Tile(value: Random().nextInt(4), row: x, col: y);
}
}
}
});
}
void makeNewTiles(_) {
setState(() {
for (int x = 0; x < 7; x++) {
for (int y = 0; y < 7; y++) {
if (tiles[x][y].getValue == 4) {
tiles[x][y] = Tile(value: Random().nextInt(4), row: x, col: y);
}
}
}
});
}
void updateRowCol() {
setState(() {
for (int i = 0; i < 7; i++) {
for (int j = 0; j < 7; j++) {
tiles[i][j].setRow(i);
tiles[i][j].setCol(j);
}
}
});
}
/*
void doTileMatchingWork() {
if (checkAdjacentTiles()) {
compute(removeTiles, null);
compute(makeNewTiles, null);
}
updateRowCol();
}*/
doTileMatchingWork() async {
ReceivePort mReceivePort = ReceivePort();
if (checkAdjacentTiles()) {
var receivePort = ReceivePort();
Isolate isolate = await Isolate.spawn<SendPort>(isolateEntry,
mReceivePort.sendPort);
SendPort anotherPort = await mReceivePort.first;
ReceivePort anotherResponseReceiePort = ReceivePort();
receivePort.listen((data) {
switch (data) {
case "makeNewTiles":
anotherPort.send(["makeNewTiles", anotherResponseReceiePort.sendPort]);
break;
case "updateRowCol":
anotherPort.send(["updateRowCol", anotherResponseReceiePort.sendPort]);
break;
}
});
}
}
void isolateEntry(SendPort mSendPort) async {
ReceivePort anotherPort = ReceivePort();
mSendPort.send(anotherPort.sendPort);
late var _ = "";
await for (var message in anotherPort) {
if (message is List) {
var function = message[0];
switch (function) {
case "removeTiles":
removeTiles(_);
anotherPort.sendPort.send("makeNewTiles");
break;
case "makeNewTiles":
makeNewTiles(_);
anotherPort.sendPort.send("updateRowCol");
break;
case "updateRowCol":
updateRowCol();
break;
}
}
}
}
void swapTiles(int i, int j, int k, int l) {
setState(() {
final temp = tiles[i][j];
tiles[i][j] = tiles[k][l];
tiles[k][l] = temp;
});
}
bool _isAnimating = false;
void animateAndSwap(int di, int dj, AxisDirection direction) {
if (_isAnimating) return;
_isAnimating = true;
// 아래쪽 코드는 flutter 3.10 (dart 3.0) 이후로 훨씬 간단히 줄일 수 있음.
final si = di +
(direction == AxisDirection.up
? -1
: direction == AxisDirection.down
? 1
: 0);
final sj = dj +
(direction == AxisDirection.left
? -1
: direction == AxisDirection.right
? 1
: 0);
final Offset dragOffset =
direction == AxisDirection.up || direction == AxisDirection.down
? Offset(0, direction == AxisDirection.up ? -1 : 1)
: Offset(direction == AxisDirection.left ? -1 : 1, 0);
final swapOffset =
direction == AxisDirection.up || direction == AxisDirection.down
? Offset(0, direction == AxisDirection.up ? 1 : -1)
: Offset(direction == AxisDirection.left ? 1 : -1, 0);
if (si < 0 || si >= 7 || sj < 0 || sj >= 7) {
_isAnimating = false;
return;
}
_offsetTweens[di][dj].end = dragOffset;
_offsetTweens[si][sj].end = swapOffset;
_controller.reset();
_controller.forward().whenCompleteOrCancel(() {
_offsetTweens[di][dj].end = Offset.zero;
_offsetTweens[si][sj].end = Offset.zero;
swapTiles(di, dj, si, sj);
_isAnimating = false;
});
}
@override
Widget build(BuildContext context) {
return GridView.builder(
shrinkWrap: true,
itemCount: 7 * 7,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 7,
),
itemBuilder: (BuildContext context, int index) {
int i = index ~/ 7;
int j = index % 7;
return SlideTransition(
position: _offsetTweens[i][j].animate(_controller),
child: GestureDetector(
// 가로 방향으로 드래그할 때 콜백 함수
onHorizontalDragEnd: (details) {
// 드래그한 방향에 따라 인접한 타일의 인덱스 설정
final dx = details.primaryVelocity!;
if (dx > 0) {
animateAndSwap(i, j, AxisDirection.right);
doTileMatchingWork();
} else if (dx < 0) {
// 왼쪽으로 드래그하면 왼쪽 타일과 교환
animateAndSwap(i, j, AxisDirection.left);
doTileMatchingWork();
}
},
onVerticalDragEnd: (details) {
// 드래그한 방향에 따라 인접한 타일의 인덱스 설정
final dy = details.primaryVelocity!;
if (dy > 0) {
animateAndSwap(i, j, AxisDirection.down);
doTileMatchingWork();
} else if (dy < 0) {
animateAndSwap(i, j, AxisDirection.up);
doTileMatchingWork();
}
},
child: tiles[i][j],
),
);
},
);
}
}
Can you address what is wrong and isolate is legitimate for this code? If not using isolate, app is freezing when it runs. The tile shows 100KB image each, and setState() and mutli for loop and 2-dimensional lists.
I have made sure the code works even before freezing observed, debugger no longer shows buttons and shortcut keys to test step by step inspection.
How can I do for?
Posted on June 13, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 29, 2024