mirror of
https://github.com/dancojocaru2000/logic-circuits-simulator.git
synced 2025-02-22 17:19:36 +02:00
Added scripting of simulation
This commit is contained in:
parent
b40811585c
commit
6e2dda60e2
1 changed files with 326 additions and 0 deletions
|
@ -1,7 +1,11 @@
|
|||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hetu_script/hetu_script.dart';
|
||||
import 'package:hetu_script/values.dart';
|
||||
import 'package:logic_circuits_simulator/components/visual_component.dart';
|
||||
import 'package:logic_circuits_simulator/models.dart';
|
||||
import 'package:logic_circuits_simulator/pages_arguments/design_component.dart';
|
||||
|
@ -42,6 +46,239 @@ class DesignComponentPage extends HookWidget {
|
|||
|
||||
useListenable(componentState.partialVisualSimulation!);
|
||||
|
||||
// Scripting
|
||||
final scriptingEnvironment = useState<Hetu?>(null);
|
||||
final loadScript = useMemoized(() => (String script) {
|
||||
scriptingEnvironment.value = Hetu();
|
||||
scriptingEnvironment.value!.init(
|
||||
externalFunctions: {
|
||||
'unload': (
|
||||
HTEntity entity, {
|
||||
List<dynamic> positionalArgs = const [],
|
||||
Map<String, dynamic> namedArgs = const {},
|
||||
List<HTType> typeArgs = const [],
|
||||
}) {
|
||||
scriptingEnvironment.value = null;
|
||||
},
|
||||
'alert': (
|
||||
HTEntity entity, {
|
||||
List<dynamic> positionalArgs = const [],
|
||||
Map<String, dynamic> namedArgs = const {},
|
||||
List<HTType> typeArgs = const [],
|
||||
}) {
|
||||
final content = positionalArgs[0] as String;
|
||||
final title = positionalArgs[1] as String? ?? 'Script Alert';
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: Text(title),
|
||||
content: Text(content),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text('OK'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
'snackBar': (
|
||||
HTEntity entity, {
|
||||
List<dynamic> positionalArgs = const [],
|
||||
Map<String, dynamic> namedArgs = const {},
|
||||
List<HTType> typeArgs = const [],
|
||||
}) {
|
||||
final content = positionalArgs[0] as String;
|
||||
final actionName = positionalArgs[1] as String?;
|
||||
final actionFunction = positionalArgs[2];
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text(content),
|
||||
action: actionName == null ? null : SnackBarAction(
|
||||
label: actionName,
|
||||
onPressed: () {
|
||||
if (actionFunction is String) {
|
||||
scriptingEnvironment.value?.invoke(actionFunction);
|
||||
}
|
||||
else if (actionFunction is HTFunction && scriptingEnvironment.value != null) {
|
||||
actionFunction.call();
|
||||
}
|
||||
},
|
||||
),
|
||||
));
|
||||
},
|
||||
'setTimeout': (
|
||||
HTEntity entity, {
|
||||
List<dynamic> positionalArgs = const [],
|
||||
Map<String, dynamic> namedArgs = const {},
|
||||
List<HTType> typeArgs = const [],
|
||||
}) {
|
||||
final millis = positionalArgs[0] as int;
|
||||
final function = positionalArgs[1];
|
||||
final pos = namedArgs['positionalArgs'] ?? [];
|
||||
final named = namedArgs['namedArgs'] ?? {};
|
||||
Future.delayed(Duration(milliseconds: millis))
|
||||
.then((_) {
|
||||
if (function is String) {
|
||||
scriptingEnvironment.value?.invoke(function, positionalArgs: pos, namedArgs: Map.castFrom(named));
|
||||
}
|
||||
else if (function is HTFunction && scriptingEnvironment.value != null) {
|
||||
function.call(positionalArgs: pos, namedArgs: Map.castFrom(named));
|
||||
}
|
||||
});
|
||||
},
|
||||
'getInputs': (
|
||||
HTEntity entity, {
|
||||
List<dynamic> positionalArgs = const [],
|
||||
Map<String, dynamic> namedArgs = const {},
|
||||
List<HTType> typeArgs = const [],
|
||||
}) {
|
||||
return componentState.currentComponent!.inputs;
|
||||
},
|
||||
'getOutputs': (
|
||||
HTEntity entity, {
|
||||
List<dynamic> positionalArgs = const [],
|
||||
Map<String, dynamic> namedArgs = const {},
|
||||
List<HTType> typeArgs = const [],
|
||||
}) {
|
||||
return componentState.currentComponent!.outputs;
|
||||
},
|
||||
'simGetInputValues': (
|
||||
HTEntity entity, {
|
||||
List<dynamic> positionalArgs = const [],
|
||||
Map<String, dynamic> namedArgs = const {},
|
||||
List<HTType> typeArgs = const [],
|
||||
}) {
|
||||
return Map.of(componentState.partialVisualSimulation!.inputsValues);
|
||||
},
|
||||
'simGetOutputValues': (
|
||||
HTEntity entity, {
|
||||
List<dynamic> positionalArgs = const [],
|
||||
Map<String, dynamic> namedArgs = const {},
|
||||
List<HTType> typeArgs = const [],
|
||||
}) {
|
||||
return Map.of(componentState.partialVisualSimulation!.outputsValues);
|
||||
},
|
||||
'simSetInput': (
|
||||
HTEntity entity, {
|
||||
List<dynamic> positionalArgs = const [],
|
||||
Map<String, dynamic> namedArgs = const {},
|
||||
List<HTType> typeArgs = const [],
|
||||
}) {
|
||||
final inputName = positionalArgs[0] as String;
|
||||
final value = positionalArgs[1] as bool;
|
||||
|
||||
return componentState.partialVisualSimulation!.modifyInput(inputName, value);
|
||||
},
|
||||
'simSetInputs': (
|
||||
HTEntity entity, {
|
||||
List<dynamic> positionalArgs = const [],
|
||||
Map<String, dynamic> namedArgs = const {},
|
||||
List<HTType> typeArgs = const [],
|
||||
}) {
|
||||
final inputs = positionalArgs[0] as Map;
|
||||
|
||||
return componentState.partialVisualSimulation!.provideInputs(inputs.map((key, value) => MapEntry(key as String, value as bool)));
|
||||
},
|
||||
'simSetInputsBinary': (
|
||||
HTEntity entity, {
|
||||
List<dynamic> positionalArgs = const [],
|
||||
Map<String, dynamic> namedArgs = const {},
|
||||
List<HTType> typeArgs = const [],
|
||||
}) {
|
||||
final inputs = componentState.currentComponent!.inputs;
|
||||
final inputsNum = positionalArgs[0] as int;
|
||||
final inputsBinary = inputsNum.toRadixString(2).padLeft(inputs.length, '0');
|
||||
final inputsMap = Map.fromIterables(inputs, inputsBinary.characters.map((c) => c == '1'));
|
||||
|
||||
return componentState.partialVisualSimulation!.provideInputs(inputsMap);
|
||||
},
|
||||
'simNextStep': (
|
||||
HTEntity entity, {
|
||||
List<dynamic> positionalArgs = const [],
|
||||
Map<String, dynamic> namedArgs = const {},
|
||||
List<HTType> typeArgs = const [],
|
||||
}) {
|
||||
return componentState.partialVisualSimulation!.nextStep();
|
||||
},
|
||||
'simRestart': (
|
||||
HTEntity entity, {
|
||||
List<dynamic> positionalArgs = const [],
|
||||
Map<String, dynamic> namedArgs = const {},
|
||||
List<HTType> typeArgs = const [],
|
||||
}) {
|
||||
return componentState.partialVisualSimulation!.restart();
|
||||
},
|
||||
'simIsPartiallySimulating': (
|
||||
HTEntity entity, {
|
||||
List<dynamic> positionalArgs = const [],
|
||||
Map<String, dynamic> namedArgs = const {},
|
||||
List<HTType> typeArgs = const [],
|
||||
}) {
|
||||
return simulatePartially.value;
|
||||
},
|
||||
'simSetPartiallySimulating': (
|
||||
HTEntity entity, {
|
||||
List<dynamic> positionalArgs = const [],
|
||||
Map<String, dynamic> namedArgs = const {},
|
||||
List<HTType> typeArgs = const [],
|
||||
}) {
|
||||
simulatePartially.value = positionalArgs[0] as bool;
|
||||
},
|
||||
},
|
||||
);
|
||||
scriptingEnvironment.value!.eval('''
|
||||
external fun unload
|
||||
external fun alert(message: String, [title])
|
||||
external fun snackBar(message: String, [actionName, actionFunction])
|
||||
external fun setTimeout(millis: int, function, {positionalArgs, namedArgs})
|
||||
external fun getInputs -> List
|
||||
external fun getOutputs -> List
|
||||
external fun simGetInputValues -> Map
|
||||
external fun simGetOutputValues -> Map
|
||||
external fun simSetInput(inputName: String, value: bool)
|
||||
external fun simSetInputs(values: Map)
|
||||
external fun simSetInputsBinary(values: int)
|
||||
external fun simNextStep
|
||||
external fun simRestart
|
||||
external fun simIsPartiallySimulating -> bool
|
||||
external fun simSetPartiallySimulating(partiallySimulating: bool)
|
||||
''');
|
||||
scriptingEnvironment.value!.eval(script, type: ResourceType.hetuModule);
|
||||
try {
|
||||
scriptingEnvironment.value!.invoke('onLoad');
|
||||
} catch (e) {
|
||||
// onLoad handling is optional
|
||||
}
|
||||
try {
|
||||
scriptingEnvironment.value!.invoke('getFunctions');
|
||||
} catch (e) {
|
||||
// Getting the callable functions of the script is mandatory
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Script Loading Failed'),
|
||||
content: const Text("The script doesn't implement the getFunctions function."),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text('OK'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
scriptingEnvironment.value = null;
|
||||
}
|
||||
}, [scriptingEnvironment.value]);
|
||||
|
||||
// Design
|
||||
final movingWidgetUpdater = useState<void Function(double dx, double dy)?>(null);
|
||||
final movingWidget = useState<dynamic>(null);
|
||||
final deleteOnDrop = useState<bool>(false);
|
||||
|
@ -533,6 +770,95 @@ class DesignComponentPage extends HookWidget {
|
|||
designSelection.value = null;
|
||||
},
|
||||
),
|
||||
if (isSimulating.value)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.description),
|
||||
tooltip: 'Scripting',
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Scripting'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ListTile(
|
||||
title: const Text('Load Script...'),
|
||||
onTap: () async {
|
||||
final nav = Navigator.of(context);
|
||||
|
||||
final selectedFiles = await FilePicker.platform.pickFiles(
|
||||
dialogTitle: "Load Script",
|
||||
// allowedExtensions: ['ht', 'txt'],
|
||||
type: FileType.any,
|
||||
);
|
||||
if (selectedFiles == null || selectedFiles.files.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final file = File(selectedFiles.files[0].path!);
|
||||
loadScript(await file.readAsString());
|
||||
} catch (e) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Script Loading Error'),
|
||||
content: Text(e.toString()),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text('OK'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
nav.pop();
|
||||
},
|
||||
),
|
||||
if (scriptingEnvironment.value != null) ...[
|
||||
const Divider(),
|
||||
for (final function in scriptingEnvironment.value!.invoke('getFunctions'))
|
||||
ListTile(
|
||||
title: Text(function),
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
try {
|
||||
scriptingEnvironment.value!.invoke(function);
|
||||
} on HTError catch (e) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Script Error'),
|
||||
content: Text(e.toString()),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('OK'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
scriptingEnvironment.value = null;
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
body: OrientationBuilder(
|
||||
|
|
Loading…
Add table
Reference in a new issue