Examples
High-Frequency Messaging
Learn how to optimize Game Framework for high-throughput scenarios with performance testing, latency measurement, and batching techniques.
Overview
The HighFrequencyDemo.cs template demonstrates:
- Stress testing messaging throughput
- Latency measurement and tracking
- Message batching effectiveness
- Throttling and rate limiting
- Performance profiling
This example is essential for games that need real-time updates like multiplayer, physics simulations, or sensor data streaming.
Setup
game sync scripts --templatesIn Unity:
- Create GameObject named
HighFrequency - Attach
HighFrequencyDemo.cs - Configure performance settings in Inspector
Performance Testing
Start a Test
Flutter Side
await controller.sendJsonMessage('HighFrequency', 'startTest', {
'messagesPerSecond': 1000,
'durationSeconds': 10,
'useBatching': true,
});Unity Side
[FlutterMethod("startTest")]
public void StartTest(TestConfig config)
{
_testRunning = true;
_messagesReceived = 0;
_messagesSent = 0;
StartCoroutine(RunPerformanceTest(config));
}
private IEnumerator RunPerformanceTest(TestConfig config)
{
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
float interval = 1f / config.messagesPerSecond;
while (stopwatch.Elapsed.TotalSeconds < config.durationSeconds)
{
SendToFlutter("onTestMessage", new TestMessage
{
id = _messagesSent++,
timestamp = Time.time
});
yield return new WaitForSeconds(interval);
}
stopwatch.Stop();
SendToFlutter("onTestComplete", new TestResults
{
messagesSent = _messagesSent,
messagesReceived = _messagesReceived,
durationSeconds = stopwatch.Elapsed.TotalSeconds,
messagesPerSecond = _messagesSent / stopwatch.Elapsed.TotalSeconds
});
}Latency Measurement
Flutter Side
class LatencyTester {
final _pendingPings = <int, DateTime>{};
final _latencies = <int>[];
Future<void> sendPing(GameEngineController controller, int id) async {
_pendingPings[id] = DateTime.now();
await controller.sendJsonMessage('HighFrequency', 'ping', {
'id': id,
'timestamp': DateTime.now().millisecondsSinceEpoch,
});
}
void handlePong(Map<String, dynamic> data) {
final id = data['id'] as int;
final sentTime = _pendingPings.remove(id);
if (sentTime != null) {
final latency = DateTime.now().difference(sentTime).inMilliseconds;
_latencies.add(latency);
print('Ping $id: ${latency}ms');
}
}
double get averageLatency {
if (_latencies.isEmpty) return 0;
return _latencies.reduce((a, b) => a + b) / _latencies.length;
}
int get minLatency => _latencies.isEmpty ? 0 : _latencies.reduce(min);
int get maxLatency => _latencies.isEmpty ? 0 : _latencies.reduce(max);
}Unity Side
[FlutterMethod("ping")]
public void Ping(PingMessage message)
{
// Immediately respond with pong
SendToFlutter("onPong", new PongMessage
{
id = message.id,
timestamp = message.timestamp,
serverTime = Time.time
});
}Message Batching
Enable Batching
protected override void Awake()
{
base.Awake();
// Enable message batching
EnableBatching = true;
BatchInterval = 0.016f; // ~60 FPS
MaxBatchSize = 100;
}Batching Performance
class BatchingTest extends StatefulWidget {
@override
State<BatchingTest> createState() => _BatchingTestState();
}
class _BatchingTestState extends State<BatchingTest> {
int _messagesSent = 0;
int _messagesReceived = 0;
Duration? _testDuration;
Future<void> _runBatchTest(bool useBatching) async {
setState(() {
_messagesSent = 0;
_messagesReceived = 0;
});
final stopwatch = Stopwatch()..start();
// Send burst of messages
for (int i = 0; i < 1000; i++) {
await _controller?.sendJsonMessage('HighFrequency', 'testMessage', {
'id': i,
'useBatching': useBatching,
});
_messagesSent++;
}
stopwatch.stop();
setState(() {
_testDuration = stopwatch.elapsed;
});
print('Sent 1000 messages in ${stopwatch.elapsedMilliseconds}ms');
print('Throughput: ${1000 / stopwatch.elapsed.inSeconds} msg/s');
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Messages Sent: $_messagesSent'),
Text('Messages Received: $_messagesReceived'),
if (_testDuration != null)
Text('Duration: ${_testDuration!.inMilliseconds}ms'),
Row(
children: [
ElevatedButton(
onPressed: () => _runBatchTest(false),
child: Text('Test Without Batching'),
),
SizedBox(width: 16),
ElevatedButton(
onPressed: () => _runBatchTest(true),
child: Text('Test With Batching'),
),
],
),
],
);
}
}Throttling
Flutter-Side Throttling
class MessageThrottler {
Timer? _throttleTimer;
DateTime? _lastSent;
final Duration minInterval;
MessageThrottler({this.minInterval = const Duration(milliseconds: 16)});
void send(GameEngineController controller, String target,
String method, Map<String, dynamic> data) {
final now = DateTime.now();
if (_lastSent == null ||
now.difference(_lastSent!) >= minInterval) {
controller.sendJsonMessage(target, method, data);
_lastSent = now;
} else {
// Schedule for later
_throttleTimer?.cancel();
_throttleTimer = Timer(minInterval, () {
controller.sendJsonMessage(target, method, data);
_lastSent = DateTime.now();
});
}
}
void dispose() {
_throttleTimer?.cancel();
}
}
// Usage
final throttler = MessageThrottler(minInterval: Duration(milliseconds: 16));
void onSliderChanged(double value) {
throttler.send(_controller!, 'Player', 'setSpeed', {
'speed': value,
});
}Unity-Side Rate Limiting
private float _lastSendTime = 0f;
private float _minSendInterval = 0.016f; // 60 FPS
private void SendUpdateIfReady(object data)
{
float now = Time.time;
if (now - _lastSendTime >= _minSendInterval)
{
SendToFlutter("onUpdate", data);
_lastSendTime = now;
}
}Performance Monitoring
Complete Flutter Example
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:gameframework/gameframework.dart';
class PerformanceMonitor extends StatefulWidget {
@override
State<PerformanceMonitor> createState() => _PerformanceMonitorState();
}
class _PerformanceMonitorState extends State<PerformanceMonitor> {
GameEngineController? _controller;
int _messagesSent = 0;
int _messagesReceived = 0;
List<int> _latencies = [];
double _throughput = 0;
Timer? _statsTimer;
@override
void initState() {
super.initState();
// Update stats every second
_statsTimer = Timer.periodic(Duration(seconds: 1), (_) {
setState(() {
_throughput = _messagesReceived.toDouble();
_messagesReceived = 0;
});
});
}
@override
void dispose() {
_statsTimer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Performance Monitor')),
body: Column(
children: [
Expanded(
child: GameWidget(
engineType: GameEngineType.unity,
config: GameEngineConfig(runImmediately: true),
onEngineCreated: (controller) {
setState(() => _controller = controller);
},
onMessage: _onMessage,
),
),
// Performance stats
Container(
color: Colors.black87,
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Performance Stats',
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 8),
_statRow('Messages Sent', _messagesSent.toString()),
_statRow('Throughput', '${_throughput.toStringAsFixed(0)} msg/s'),
_statRow('Avg Latency', _avgLatency),
_statRow('Min Latency', _minLatency),
_statRow('Max Latency', _maxLatency),
],
),
),
// Controls
Padding(
padding: EdgeInsets.all(8),
child: Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: _startStressTest,
child: Text('Start Stress Test'),
),
),
SizedBox(width: 8),
Expanded(
child: ElevatedButton(
onPressed: _runLatencyTest,
child: Text('Latency Test'),
),
),
],
),
),
],
),
);
}
Widget _statRow(String label, String value) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label, style: TextStyle(color: Colors.grey)),
Text(value, style: TextStyle(color: Colors.white)),
],
),
);
}
String get _avgLatency {
if (_latencies.isEmpty) return '-';
final avg = _latencies.reduce((a, b) => a + b) / _latencies.length;
return '${avg.toStringAsFixed(1)}ms';
}
String get _minLatency {
if (_latencies.isEmpty) return '-';
return '${_latencies.reduce((a, b) => a < b ? a : b)}ms';
}
String get _maxLatency {
if (_latencies.isEmpty) return '-';
return '${_latencies.reduce((a, b) => a > b ? a : b)}ms';
}
void _onMessage(GameEngineMessage message) {
_messagesReceived++;
if (message.method == 'onPong') {
final data = jsonDecode(message.data);
final latency = DateTime.now().millisecondsSinceEpoch -
(data['timestamp'] as int);
setState(() {
_latencies.add(latency);
if (_latencies.length > 100) {
_latencies.removeAt(0);
}
});
}
}
Future<void> _startStressTest() async {
await _controller?.sendJsonMessage('HighFrequency', 'startTest', {
'messagesPerSecond': 1000,
'durationSeconds': 10,
'useBatching': true,
});
}
Future<void> _runLatencyTest() async {
setState(() => _latencies.clear());
for (int i = 0; i < 100; i++) {
await _controller?.sendJsonMessage('HighFrequency', 'ping', {
'id': i,
'timestamp': DateTime.now().millisecondsSinceEpoch,
});
await Future.delayed(Duration(milliseconds: 10));
}
}
}Best Practices
1. Use Batching for High Frequency
// Enable for position updates, sensor data, etc.
EnableBatching = true;
BatchInterval = 0.016f; // 60 FPS2. Throttle User Input
// Throttle slider changes
void onSliderChanged(double value) {
throttler.send(_controller!, 'Player', 'setParameter', {
'value': value,
});
}3. Monitor Performance
// Track message throughput
int _lastCount = 0;
Timer.periodic(Duration(seconds: 1), (_) {
final throughput = _messageCount - _lastCount;
print('Throughput: $throughput msg/s');
_lastCount = _messageCount;
});4. Use Appropriate Message Types
// High frequency → Binary (if applicable)
// Medium frequency → JSON with batching
// Low frequency → JSON without batchingWith proper optimization, Game Framework can handle thousands of messages per second!
What You'll Learn
- ✅ Performance testing and profiling
- ✅ Latency measurement
- ✅ Message batching
- ✅ Throttling and rate limiting
- ✅ Throughput optimization