Browse Source

Added scripting of simulation

master
Kenneth Bruen 2 years ago
parent
commit
6e2dda60e2
Signed by: kbruen
GPG Key ID: C1980A470C3EE5B1
  1. 326
      lib/pages/design_component.dart

326
lib/pages/design_component.dart

@ -1,7 +1,11 @@
import 'dart:io';
import 'dart:math'; import 'dart:math';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.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/components/visual_component.dart';
import 'package:logic_circuits_simulator/models.dart'; import 'package:logic_circuits_simulator/models.dart';
import 'package:logic_circuits_simulator/pages_arguments/design_component.dart'; import 'package:logic_circuits_simulator/pages_arguments/design_component.dart';
@ -42,6 +46,239 @@ class DesignComponentPage extends HookWidget {
useListenable(componentState.partialVisualSimulation!); 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 movingWidgetUpdater = useState<void Function(double dx, double dy)?>(null);
final movingWidget = useState<dynamic>(null); final movingWidget = useState<dynamic>(null);
final deleteOnDrop = useState<bool>(false); final deleteOnDrop = useState<bool>(false);
@ -533,6 +770,95 @@ class DesignComponentPage extends HookWidget {
designSelection.value = null; 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( body: OrientationBuilder(

Loading…
Cancel
Save