Add search_resource_async for visualisation
This commit is contained in:
parent
459bef9c45
commit
6c0f1dcbce
|
@ -5,7 +5,7 @@ Tool for helping with factory planning.
|
|||
|
||||
## Installation
|
||||
|
||||
Tested are Python 3.11, PySide2 5.15.11 and Qt 5.15.11. The later two are necessary for the visualisation. Install all Python requirements by using
|
||||
Tested are Python 3.11, Firefox 122, SQLite 3.45.1, PySide2 5.15.11 and Qt 5.15.11. The later two are necessary for the visualisation. Install all Python requirements by using
|
||||
|
||||
```sh
|
||||
pipenv --site-packages sync
|
||||
|
@ -39,6 +39,5 @@ Thanks to [NodeGraphQt](https://github.com/jchanvfx/NodeGraphQt) a graph base vi
|
|||
The visualisation window can be opened with e.g.
|
||||
|
||||
```sh
|
||||
pipenv run fetch 'Plastic'
|
||||
pipenv run vis 'Plastic'
|
||||
```
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import re
|
||||
import sys
|
||||
from concurrent.futures import Future
|
||||
from typing import Callable
|
||||
|
||||
import click
|
||||
import sqlalchemy
|
||||
|
@ -18,7 +20,7 @@ from sqlalchemy.orm import Session
|
|||
|
||||
from factorygame.data.common import chose_resource, resource_needs_update, chose_recipe
|
||||
from factorygame.data.sfp import SatisfactoryPlus, DEFAULT_IGNORE_FACTORIES
|
||||
from .models import Recipe, Resource, ResourceFlow
|
||||
from .models import Recipe, Resource, ResourceFlow, Base
|
||||
|
||||
WORLD_INPUT_PORT_NAME = "World Input"
|
||||
WORLD_OUTPUT_PORT_NAME = "World Output"
|
||||
|
@ -105,7 +107,44 @@ class AsyncResourceFetcher(QThread):
|
|||
resource = session.scalars(Resource.by_label(self.resource_label)).one()
|
||||
resource = data_provider.update_resource_recipes(session=session, resource=resource)
|
||||
if self.debug:
|
||||
print("Updated resource in separate thread", resource)
|
||||
print("AsyncResourceFetcher updated resource in separate thread", resource)
|
||||
session.commit()
|
||||
|
||||
|
||||
class AsyncResourceFinder(QThread):
|
||||
def __init__(self, parent, resource_label: str, engine: sqlalchemy.Engine, prompt: Callable, debug: bool):
|
||||
super().__init__(parent=parent)
|
||||
self.resource_label = resource_label
|
||||
self.engine = engine
|
||||
self.debug = debug
|
||||
self.prompt = prompt
|
||||
self.result = Future()
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
with (
|
||||
Session(self.engine) as session,
|
||||
SatisfactoryPlus(ignore_factories=DEFAULT_IGNORE_FACTORIES, debug=self.debug) as data_provider,
|
||||
):
|
||||
search_result_future = data_provider.search_for_resource(
|
||||
session=session, search=self.resource_label, prompt=self.prompt
|
||||
)
|
||||
search_result = search_result_future.result(timeout=60)
|
||||
if search_result is None:
|
||||
if self.debug:
|
||||
print("AsyncResourceFinder search completed, but no result")
|
||||
self.result.set_result(None)
|
||||
return
|
||||
|
||||
resource, exists_in_db = search_result
|
||||
if not exists_in_db or resource_needs_update(resource):
|
||||
resource = data_provider.update_resource_recipes(session=session, resource=resource)
|
||||
if self.debug:
|
||||
print("AsyncResourceFinder updated resource", resource)
|
||||
self.result.set_result(resource)
|
||||
session.commit()
|
||||
except:
|
||||
self.result.set_exception(sys.exception())
|
||||
|
||||
|
||||
class NodeSlider(NodeBaseWidget):
|
||||
|
@ -410,6 +449,7 @@ class GraphController(QObject):
|
|||
self.debug = debug
|
||||
|
||||
self.engine = create_engine("sqlite:///file.db", echo=debug)
|
||||
Base.metadata.create_all(bind=self.engine)
|
||||
self.graph = NodeGraph(parent=self)
|
||||
self.graph.widget.resize(1280, 720)
|
||||
self.graph.register_node(Machine)
|
||||
|
@ -421,26 +461,26 @@ class GraphController(QObject):
|
|||
|
||||
self.graph.port_connected.connect(self.on_port_connected)
|
||||
|
||||
self.prompt_dialog = QInputDialog(parent=self.graph.widget)
|
||||
self.prompt_dialog.setStyleSheet(generate_fgbg_stylesheet())
|
||||
self.prompt_dialog.setModal(True)
|
||||
self._dialog_future = Future()
|
||||
self.prompt_dialog.rejected.connect(lambda: self._dialog_future.set_result(None))
|
||||
self.prompt_dialog.accepted.connect(lambda: self._dialog_future.set_result(self.prompt_dialog.textValue()))
|
||||
|
||||
self.loading_dialog = QDialog(parent=self.graph.widget, f=(Qt.Dialog | Qt.ToolTip))
|
||||
self.loading_dialog.setStyleSheet(generate_fgbg_stylesheet(0.5))
|
||||
self.loading_dialog.setModal(True)
|
||||
self.loading_dialog.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
label = QLabel("Loading…", self.loading_dialog)
|
||||
label.setAlignment(Qt.AlignCenter)
|
||||
|
||||
def add_machine_from_search(self, search: str):
|
||||
recipe_selected_future = Future()
|
||||
|
||||
def resource_selected_cb(resource_future: Future[Resource | None]):
|
||||
resource = resource_future.result()
|
||||
def resource_found_db(resource: Resource | None):
|
||||
if resource is None:
|
||||
# FIXME: use concurrent resource searching/fetching
|
||||
# ret = data_provider.search_for_resource(session=session, search=search)
|
||||
ret = None
|
||||
if ret is None:
|
||||
print("Resource not found")
|
||||
recipe_selected_future.set_result(None)
|
||||
return
|
||||
|
||||
resource, exists_in_db = ret
|
||||
if not exists_in_db:
|
||||
print("Resource unknown")
|
||||
recipe_selected_future.set_result(None)
|
||||
return
|
||||
assert resource is not None
|
||||
return
|
||||
|
||||
def chose_recipe_async():
|
||||
with Session(self.engine) as session:
|
||||
|
@ -454,10 +494,13 @@ class GraphController(QObject):
|
|||
else:
|
||||
chose_recipe_async()
|
||||
|
||||
def select_recipe_async():
|
||||
with Session(self.engine) as session:
|
||||
resource_future = chose_resource(session=session, resource_label=search, prompt=self.dialog_prompt)
|
||||
resource_future.add_done_callback(resource_selected_cb)
|
||||
def resource_selected_or_search_cb(resource_future: Future[Resource | None]):
|
||||
resource = resource_future.result()
|
||||
if resource is None:
|
||||
search_future = self.search_resource_async(resource_label=search)
|
||||
search_future.add_done_callback(lambda fut: resource_found_db(fut.result()))
|
||||
else:
|
||||
resource_found_db(resource)
|
||||
|
||||
def recipe_selected_cb(recipe_future: Future[Recipe | None]):
|
||||
recipe = recipe_future.result()
|
||||
|
@ -473,7 +516,9 @@ class GraphController(QObject):
|
|||
self.graph.center_on([self.global_input, recipe_machine, self.global_output])
|
||||
|
||||
recipe_selected_future.add_done_callback(recipe_selected_cb)
|
||||
select_recipe_async()
|
||||
with Session(self.engine) as session:
|
||||
resource_future = chose_resource(session=session, resource_label=search, prompt=self.dialog_prompt)
|
||||
resource_future.add_done_callback(resource_selected_or_search_cb)
|
||||
|
||||
def show(self):
|
||||
self.graph.widget.show()
|
||||
|
@ -597,44 +642,59 @@ class GraphController(QObject):
|
|||
debug=self.debug,
|
||||
)
|
||||
fetch_future = Future()
|
||||
self.dialog_loading(fetch_future)
|
||||
self.loading_dialog.show()
|
||||
fetch_future.add_done_callback(lambda fut: self.loading_dialog.hide())
|
||||
fetcher_thread.finished.connect(lambda: fetch_future.set_result(None))
|
||||
fetcher_thread.start()
|
||||
return fetch_future
|
||||
|
||||
def search_resource_async(self, resource_label) -> Future[Resource]:
|
||||
if self.debug:
|
||||
print("Searching resource", resource_label)
|
||||
|
||||
loading_future = Future()
|
||||
|
||||
def show_prompt(*args, **kwargs):
|
||||
loading_future.set_result(None)
|
||||
return self.dialog_prompt(*args, **kwargs)
|
||||
|
||||
fetcher_thread = AsyncResourceFinder(
|
||||
parent=self.graph,
|
||||
resource_label=resource_label,
|
||||
engine=self.engine,
|
||||
prompt=show_prompt,
|
||||
debug=self.debug,
|
||||
)
|
||||
self.loading_dialog.show()
|
||||
loading_future.add_done_callback(lambda fut: self.loading_dialog.hide())
|
||||
fetcher_thread.start()
|
||||
return fetcher_thread.result
|
||||
|
||||
def dialog_prompt(self, options: dict[int, str], text: str, default: int) -> Future[int | None]:
|
||||
assert self.prompt_dialog.isHidden(), "Prompt dialog already visible"
|
||||
if self.debug:
|
||||
print("Displaying QInputDialog with options:", ", ".join(options.values()))
|
||||
|
||||
dialog = QInputDialog(parent=self.graph.widget)
|
||||
dialog.setStyleSheet(generate_fgbg_stylesheet())
|
||||
dialog.setModal(True)
|
||||
dialog.setLabelText(text)
|
||||
self.prompt_dialog.setLabelText(text)
|
||||
if default in options:
|
||||
dialog.setTextValue(options[default])
|
||||
self.prompt_dialog.setTextValue(options[default])
|
||||
reversed_options: dict[str, int] = {value: key for key, value in options.items()}
|
||||
dialog.setComboBoxItems(reversed_options.keys())
|
||||
self.prompt_dialog.setComboBoxItems(reversed_options.keys())
|
||||
|
||||
ret = Future()
|
||||
dialog.rejected.connect(lambda: ret.set_result(None))
|
||||
dialog.accepted.connect(lambda: ret.set_result(reversed_options[dialog.textValue()]))
|
||||
dialog.show()
|
||||
self._dialog_future = Future()
|
||||
|
||||
def map_result(fut: Future):
|
||||
result = fut.result()
|
||||
if result is None:
|
||||
ret.set_result(None)
|
||||
else:
|
||||
ret.set_result(reversed_options[result])
|
||||
|
||||
self._dialog_future.add_done_callback(map_result)
|
||||
self.prompt_dialog.show()
|
||||
return ret
|
||||
|
||||
def dialog_loading(self, future: Future):
|
||||
dialog = QDialog(parent=self.graph.widget, f=(Qt.Dialog | Qt.ToolTip))
|
||||
dialog.setStyleSheet(generate_fgbg_stylesheet(0.5))
|
||||
dialog.setModal(True)
|
||||
dialog.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
label = QLabel("Loading…", dialog)
|
||||
label.setAlignment(Qt.AlignCenter)
|
||||
dialog.show()
|
||||
|
||||
def remove_dialog():
|
||||
dialog.hide()
|
||||
dialog.destroy()
|
||||
|
||||
future.add_done_callback(lambda fut: remove_dialog())
|
||||
|
||||
|
||||
@click.command
|
||||
@click.option("--debug", is_flag=True)
|
||||
|
|
Loading…
Reference in a new issue