You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

254 lines
8.2 KiB

import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:logic_circuits_simulator/models.dart';
import 'package:logic_circuits_simulator/utils/simulation.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';
import 'package:tuple/tuple.dart';
class ComponentState extends ChangeNotifier {
ProjectEntry? _currentProject;
ComponentEntry? _currentComponent;
Wiring _wiring = const Wiring(instances: [], wires: []);
Wiring? _wiringDraft;
Design _design = const Design(components: [], wires: [], inputs: [], outputs: []);
Design? _designDraft;
SimulatedComponent? _simulatedComponent;
PartialVisualSimulation? _partialVisualSimulation;
final Map<String, Tuple2<ProjectEntry, ComponentEntry>> _dependenciesMap = {};
ProjectEntry? get currentProject => _currentProject;
ComponentEntry? get currentComponent => _currentComponent;
Wiring get wiring => _wiring;
Wiring get wiringDraft => _wiringDraft ?? _wiring;
Design get design => _design;
Design get designDraft => _designDraft ?? _design;
PartialVisualSimulation? get partialVisualSimulation => _partialVisualSimulation;
Future<SimulatedComponent> _onRequiredDependency(String depId) async {
final t = _dependenciesMap[depId]!;
final proj = t.item1;
final comp = t.item2;
final state = comp.visualDesigned ? ComponentState() : null;
if (state != null) {
await state.setCurrentComponent(
project: proj,
component: comp,
onDependencyNeeded: (projId, compId) async => _dependenciesMap['${projId == "self" ? proj.projectId : projId}/$compId'],
);
}
return SimulatedComponent(
project: proj,
component: comp,
onRequiredDependency: _onRequiredDependency,
state: state,
);
}
Future<Directory> _getComponentDir() async {
if (_currentProject == null) {
throw Exception('Cannot get component directory without knowing project');
}
if (_currentComponent == null) {
throw Exception('Cannot get component directory of null');
}
final appDir = await getApplicationDocumentsDirectory();
final result = Directory(path.join(appDir.path, 'LogicCircuitsSimulator', 'projects', _currentProject!.projectId, 'components', _currentComponent!.componentId));
if (!await result.exists()) {
await result.create(recursive: true);
}
return result;
}
Future<File> _getWiringFile() async {
final result = File(path.join((await _getComponentDir()).path, 'wiring.json'));
return result;
}
Future<File> _getDesignFile() async {
final result = File(path.join((await _getComponentDir()).path, 'design.json'));
return result;
}
Future<void> _loadComponentFiles() async {
final wiringFile = await _getWiringFile();
if (!await wiringFile.exists()) {
_wiring = const Wiring(instances: [], wires: []);
await wiringFile.writeAsString(jsonEncode(_wiring));
}
else {
_wiring = Wiring.fromJson(jsonDecode(await wiringFile.readAsString()));
}
_wiringDraft = null;
final designFile = await _getDesignFile();
if (!await designFile.exists()) {
_design = const Design(components: [], wires: [], inputs: [], outputs: []);
await designFile.writeAsString(jsonEncode(_design));
}
else {
_design = Design.fromJson(jsonDecode(await designFile.readAsString()));
}
_designDraft = null;
}
Future<void> setCurrentComponent({
required ProjectEntry project,
required ComponentEntry component,
required Future<Tuple2<ProjectEntry, ComponentEntry>?> Function(String projectId, String componentId) onDependencyNeeded,
}) async {
_dependenciesMap.clear();
_simulatedComponent = null;
_currentProject = project;
_currentComponent = component;
// Load dependencies
final unsatisfiedDependencies = <String>[];
final neededDependencies = component.dependencies.toList();
while (neededDependencies.isNotEmpty) {
final tmp = neededDependencies.toList();
neededDependencies.clear();
for (final depId in tmp) {
if (!hasDependency(depId)) {
final splitted = depId.split('/');
final maybeDep = await onDependencyNeeded(splitted[0], splitted[1]);
if (maybeDep == null) {
unsatisfiedDependencies.add(depId);
}
else {
addDependency(depId, maybeDep);
neededDependencies.addAll(
maybeDep.item2.dependencies
.map((depId) {
final splitted = depId.split('/');
final projectId = splitted[0] == 'self' ? maybeDep.item1.projectId : splitted[0];
return '$projectId/${splitted[1]}';
})
.where((depId) => !hasDependency(depId))
);
}
}
}
}
if (unsatisfiedDependencies.isNotEmpty) {
throw DependenciesNotSatisfiedException(dependencies: unsatisfiedDependencies);
}
await _loadComponentFiles();
await recreatePartialSimulation();
notifyListeners();
}
void addDependency(String depId, Tuple2<ProjectEntry, ComponentEntry> dependency, {bool modifyCurrentComponent = false}) {
_dependenciesMap[depId] = dependency;
if (modifyCurrentComponent && _currentComponent?.dependencies.contains(depId) == false) {
_currentComponent = _currentComponent?.copyWith(
dependencies: (_currentComponent?.dependencies ?? []) + [depId],
);
}
}
void removeDependency(String depId, {bool modifyCurrentComponent = false}) {
_dependenciesMap.remove(depId);
if (modifyCurrentComponent && _currentComponent?.dependencies.contains(depId) == true) {
_currentComponent = _currentComponent?.copyWith(
dependencies: _currentComponent?.dependencies.where((dep) => dep != depId).toList() ?? [],
);
}
}
Future<void> recreatePartialSimulation() async {
if (_currentComponent!.visualDesigned) {
_partialVisualSimulation = await PartialVisualSimulation.init(
project: _currentProject!,
component: _currentComponent!,
state: this,
onRequiredDependency: _onRequiredDependency,
);
}
notifyListeners();
}
bool hasDependency(String depId) => _dependenciesMap.containsKey(depId);
void noComponent() {
_dependenciesMap.clear();
_currentProject = null;
_currentComponent = null;
_wiring = const Wiring(instances: [], wires: []);
_design = const Design(components: [], wires: [], inputs: [], outputs: []);
_wiringDraft = _designDraft = null;
_simulatedComponent = null;
_partialVisualSimulation = null;
notifyListeners();
}
Tuple2<ProjectEntry, ComponentEntry> getMetaByInstance(String instanceId) {
for (final instance in wiring.instances) {
if (instance.instanceId == instanceId) {
return _dependenciesMap[instance.componentId]!;
}
}
throw Exception('Instance $instanceId not found in the dependencies map');
}
Future<Map<String, bool>> simulate(Map<String, bool> inputs) async {
_simulatedComponent ??= SimulatedComponent(
project: _currentProject!,
component: _currentComponent!,
onRequiredDependency: _onRequiredDependency,
state: this,
);
return _simulatedComponent!.simulate(inputs);
}
Future<Design> updateDesign(Design newDesign, {bool commit = true}) async {
if (commit) {
_design = newDesign;
_designDraft = null;
final designFile = await _getDesignFile();
await designFile.writeAsString(jsonEncode(newDesign));
}
else {
_designDraft = newDesign;
}
notifyListeners();
return designDraft;
}
Future<Wiring> updateWiring(Wiring newWiring, {bool commit = true}) async {
if (commit) {
_wiring = newWiring;
_wiringDraft = null;
final wiringFile = await _getWiringFile();
await wiringFile.writeAsString(jsonEncode(newWiring));
}
else {
_wiringDraft = newWiring;
}
notifyListeners();
return wiringDraft;
}
}
class DependenciesNotSatisfiedException with Exception {
final List<String> dependencies;
const DependenciesNotSatisfiedException({required this.dependencies});
@override
String toString() {
return 'DependenciesNotSatisfiedException(${dependencies.join(", ")})';
}
}