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
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(", ")})'; |
|
} |
|
}
|
|
|