import 'dart:io';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'console.dart';
import 'logs.dart';
import 'project.dart';
import 'settings.dart';
import 'profiler.dart';
import 'serializer.dart';
const maxConsoleEntries = 15000;
const maxProfileFrames = 15000;
void main() {
runApp(MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => ProjectConfig()),
],
child: const AlligatorApp(),
));
}
class AlligatorApp extends StatelessWidget {
const AlligatorApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Alligator Editor',
locale: const Locale('en', 'US'),
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
brightness: Brightness.dark,
seedColor: Colors.green,
),
useMaterial3: true,
),
home: const MyHomePage(),
);
}
}
class RunButtons extends StatelessWidget {
final bool isRunning;
final Function(BuildContext) onStart;
final Function(BuildContext) onStop;
const RunButtons(this.isRunning, {required this.onStart, required this.onStop, super.key});
@override
Widget build(BuildContext context) {
if (!this.isRunning) {
return TextButton.icon(
onPressed: () => this.onStart(context),
icon: const Icon(Icons.play_arrow),
label: const Text('Run'),
style: TextButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: const BorderRadius.all(Radius.circular(8)),
side: BorderSide(
color: Theme.of(context).colorScheme.primary,
width: 3,
),
),
),
);
} else {
return TextButton.icon(
onPressed: () => this.onStop(context),
icon: const Icon(Icons.stop),
label: const Text('Stop'),
style: TextButton.styleFrom(
foregroundColor: Theme.of(context).colorScheme.error,
shape: RoundedRectangleBorder(
borderRadius: const BorderRadius.all(Radius.circular(8)),
side: BorderSide(
color: Theme.of(context).colorScheme.error,
width: 3,
),
),
),
);
}
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final List<ConsoleEntry> _consoleEntries = [];
static const List<Tab> tabs = <Tab>[
Tab(text: 'Project'),
Tab(text: 'Log'),
Tab(text: 'Profiler'),
Tab(text: 'Console'),
Tab(text: 'Settings'),
];
Process? _runningGame;
final StringBuffer _buffer = StringBuffer();
final List<LogEntry> _logEntries = [];
DateTime? _previousFrameTime;
int _nextFrameId = 0;
final List<ProfileFrame> _frames = [];
void _sendMessage(String msg) {
this._runningGame?.stdin.writeln(msg);
setState(() {
_consoleEntries.add(ConsoleEntry(
text: msg,
generatedByRuntime: false,
timeGenerated: DateTime.now(),
));
});
}
void _parseMessage(String message) {
final args = message.split(' ');
if (args[0] == 'runtimelog') {
final logType = LogTypeExtension.parse(args[1]);
final [fileName, lineNumberS] = args[2].split(':');
final lineNumber = int.parse(lineNumberS);
final logMsg = args.sublist(3).join(' ');
setState(() => this._logEntries.add(LogEntry(false, logType, fileName, lineNumber, logMsg)));
} else if (args[0] == 'scriptlog') {
final logType = LogTypeExtension.parse(args[1]);
final [fileName, lineNumberS] = args[2].split(':');
final lineNumber = int.parse(lineNumberS);
final logMsg = args.sublist(3).join(' ');
setState(() => this._logEntries.add(LogEntry(true, logType, fileName, lineNumber, logMsg)));
} else if (args[0] == 'frametime') {
if (_previousFrameTime == null) {
_previousFrameTime = DateTime.fromMicrosecondsSinceEpoch(int.parse(args[1]));
return;
}
final id = _nextFrameId++;
final start = _previousFrameTime!;
final end = DateTime.fromMicrosecondsSinceEpoch(int.parse(args[1]));
_previousFrameTime = end;
setState(() {
_frames.add(ProfileFrame(id, start, end, []));
if (_frames.length >= maxProfileFrames) {
_frames.removeRange(0, _frames.length - maxProfileFrames);
}
});
}
}
void _startGame(BuildContext cx) async {
ProjectConfig projectConfig = Provider.of(cx, listen: false);
final gameConfig = AlligatorGame.fromConfig(projectConfig: projectConfig).toJson();
this._runningGame?.kill();
this._buffer.clear();
_consoleEntries.clear();
this._logEntries.clear();
this._previousFrameTime = null;
this._nextFrameId = 0;
this._frames.clear();
this._runningGame = await Process.start("alligator", ["--config", gameConfig, "--debug"]);
this._runningGame!.exitCode.then((value) => this._runningGame = null);
this._runningGame!.stdout.listen(
(event) async {
await Future.delayed(const Duration(milliseconds: 16));
for (final code in event) {
final char = String.fromCharCode(code);
if (char == "\n") {
final message = this._buffer.toString();
setState(
() => _consoleEntries.add(ConsoleEntry(
text: message,
generatedByRuntime: true,
timeGenerated: DateTime.now(),
)),
);
this._buffer.clear();
_parseMessage(message);
} else {
this._buffer.write(char);
}
setState(() {
if (_consoleEntries.length > maxConsoleEntries) {
_consoleEntries.removeRange(0, _consoleEntries.length - maxConsoleEntries);
}
});
}
},
onDone: () {
this.setState(() => this._runningGame = null);
},
);
}
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return DefaultTabController(
length: tabs.length,
child: Scaffold(
appBar: AppBar(
title: const Text("Alligator Editor"),
bottom: const TabBar(
tabs: tabs,
),
actions: [
RunButtons(
this._runningGame != null,
onStart: this._startGame,
onStop: (_) => this._runningGame!.kill(),
),
const SizedBox(width: 20),
],
),
body: TabBarView(
children: [
const ProjectPage(),
LogPage(this._logEntries),
ProfilerPage(_frames),
ConsolePage(_consoleEntries, this._sendMessage),
const SettingsPage(),
],
),
),
);
}
}
|