import 'dart:async'; import 'package:grpc/grpc.dart'; import 'package:trackoffical_app/exception/exception.dart'; import 'package:trackoffical_app/logger.dart'; import 'package:trackoffical_app/model.dart'; import 'package:trackoffical_app/service/api.dart'; import 'package:trackoffical_app/service/app.dart'; import 'package:trackoffical_app/service/app_map.dart'; import 'package:trackoffical_app/service/game/game_instance.dart'; import 'package:trackoffical_app/service/image.dart'; import 'package:trackoffical_app/utils.dart'; import 'package:protobuf/protobuf.dart'; import '../../model/game_map.dart'; import '../../model/game_person_data.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:trackoffical_app/pb.dart' as pb; class GameInstanceGuardian extends GameInstance { GameInstanceGuardian({required this.underGuardianId}); @override Future onInit() async { if (underGuardianId >= 0) { _workWatch(); } else { // 测试 _gameMapInitState = 'ok'; } await _waitGameMapOk(); if (!isActive) { throw FutureCancel(); } await gameMapData.loadMemory(onReceiveProgress: (int count, int total) {}); if (!isActive) { throw FutureCancel(); } for(var p in gamePersonData){ await p.initControlPointWantSequence(gameMapData); } addTimer(100.milliseconds, () { for (var one in gamePersonData) { final startAt = one.startAt; final endAt = one.endAt ?? App.to.now; var duration = startAt != null ? endAt.difference(startAt) : 0.seconds; one.duration.value = duration; } }); } void _workWatch() async { debug('watch: 开始'); while (isActive) { try { _watchStream = ApiService.to.guardianWatch(underGuardianId); await for (var msg in _watchStream!.stream) { debug('rcv: ${msg.meta.typeUrl}'); msg.handleType(pb.GetInGameDataReply(), (userId, data) { onPersonInit(userId, data); onMapInit(data.data); }); msg.handleType(pb.GameGpsUploadRequest(), onGameGpsUploadRequest); msg.handleType(pb.GameFinishRequest(), onGameFinish); msg.handleType(pb.GameSaveUploadRequest(), onGameSaveUploadRequest); msg.handleType(pb.GameHrUploadRequest(), onGameHrUploadRequest); msg.handleType(pb.GameCleUploadRequest(), onGameCleUploadRequest); msg.handleType(pb.PupilInGameInfoOther(), onPupilInGameInfoOther); } } on GrpcError catch (e) { if (e.code == StatusCode.unavailable) { warn('watch: 断线:$e'); await 3.seconds.delay(); }else if (e.code == StatusCode.cancelled) { break; } else { warn('watch: GrpcError:$e'); _gameMapInitState = e; break; } } catch (e) { debug('watch: err:$e'); _gameMapInitState = e; break; } } if (Get.currentRoute == '/GuardianWatchGameView') { Get.back(); } debug('watch: 结束'); } void onGameSaveUploadRequest(int userId, pb.GameSaveUploadRequest data) { final old = _gamePersonDataGetByUserId(userId); if (old != null) { old.gameSrcState.update((val) { val?.pbGameSave = data.gameSave; }); old.cpWantSequenceRefresh(gameMapData); } } void onPersonInit(int userId, pb.GetInGameDataReply data) { final old = _gamePersonDataGetByUserId(userId); if (old == null) { final headImage = data.data.image.toModel(); final state = data.data.toGameState(); state.pbGameSave = data.save; final person = GamePersonData() ..userId = userId ..userHead.value = headImage ..userName = data.data.name ..gameSrcState.value = state; headImage.loadMemory().then((value) => person.userHead.update((val) {})); gamePersonData.add(person); person.initControlPointWantSequence(gameMapData); } } Future onMapInit(pb.GameData data) async { if (_gameMapInitState == 'ok') { return; } gameMapData = data.mapZip.toGameMap(); _gameMapInitState = 'ok'; } void onGameGpsUploadRequest(int userId, pb.GameGpsUploadRequest data) async { final p = _gamePersonDataGetByUserId(userId); if (p != null) { final gpsList = data.gameGpsInfos.map((e) => e.toModel()).toList(); p.myPositionHistory.addAll(gpsList); final onMapList = []; for (var one in gpsList) { onMapList.add(await gameMapData.worldToPixel(one)); } p.myPositionOnMapHistory.addAll(onMapList); p.pacePerKm.value = data.pace.seconds; p.myPositionHistoryLen.value = data.distance.meter; } } void onGameHrUploadRequest(int userId, pb.GameHrUploadRequest data)async { final p = _gamePersonDataGetByUserId(userId); if (p != null) { if(data.gameHrInfos.isNotEmpty){ final hr = data.gameHrInfos.last; p.heartRate.value = hr.hr; } } } void onGameCleUploadRequest(int userId, pb.GameCleUploadRequest data)async { final p = _gamePersonDataGetByUserId(userId); if (p != null) { p.ck.value = data.ck.toDouble(); p.kCal.value = data.cle.toDouble()/1000; p.hrMean.value = data.avgHr; p.hrMax.value = data.maxHr; p.stepCount.value = data.stepNum; p.heartRatePercent.value = data.heartRatePercent.toDouble(); } } void onPupilInGameInfoOther(int userId, pb.PupilInGameInfoOther data)async { final p = _gamePersonDataGetByUserId(userId); if (p != null) { p.beanCount.value = data.sysPoint; } } void onGameFinish(int userId, pb.GameFinishRequest data) async { _gamePersonDataRemoveByUserId(userId); } GamePersonData? _gamePersonDataGetByUserId(int userId) { for (var one in gamePersonData) { if (one.userId == userId) { return one; } } return null; } void _gamePersonDataRemoveByUserId(int userId) { var index = -1; for (var i = 0; i < gamePersonData.length; i++) { if (gamePersonData[i].userId == userId) { index = i; break; } } if (index >= 0) { gamePersonData.removeAt(index); } } Future _waitGameMapOk() async { while (isActive && _gameMapInitState == null) { await 10.milliseconds.delay(); } if (_gameMapInitState == 'ok') { return; } throw _gameMapInitState; } @override Future onClose() async { _watchStream?.stream.cancel(); _watchStream?.channel.terminate(); } final gamePersonData = [].obs; var gameMapData = GameMap(); final int underGuardianId; StreamGuardianWatch? _watchStream; dynamic _gameMapInitState; } extension MsgHandle on pb.PupilInGameWatchReply { void handleType( T instance, void Function(int userId, T data) handler) { if (meta.canUnpackInto(instance)) { final data = meta.unpackInto(instance); handler(oId, data); } } }