From ae33b2c75ec0e03fd1d80241a4c4b29acadb517c Mon Sep 17 00:00:00 2001 From: "bozo.kopic" Date: Mon, 3 Jan 2022 00:58:17 +0100 Subject: calculate native implementation --- playground/calculate.sh | 6 +-- playground/generate-output.sh | 9 +++++ playground/generate_output.sh | 9 ----- schemas/openapi.yaml | 5 +++ src_c/common.h | 2 +- src_js/common.js | 5 ++- src_js/dragger.js | 2 +- src_js/states.js | 1 + src_js/vt.js | 15 +++++--- src_py/opcut/main.py | 90 +++++++++++++++++++++++++------------------ src_py/opcut/server.py | 64 +++++++++++++++++++++++++++--- 11 files changed, 142 insertions(+), 66 deletions(-) create mode 100755 playground/generate-output.sh delete mode 100755 playground/generate_output.sh diff --git a/playground/calculate.sh b/playground/calculate.sh index bff0e14..9368a6d 100755 --- a/playground/calculate.sh +++ b/playground/calculate.sh @@ -3,6 +3,6 @@ . $(dirname -- "$0")/env.sh exec $PYTHON -m opcut calculate \ - --params $RUN_PATH/params.json \ - --result $RUN_PATH/result.json \ - "$@" + --method forward_greedy \ + --output $RUN_PATH/result.json \ + $RUN_PATH/params.json diff --git a/playground/generate-output.sh b/playground/generate-output.sh new file mode 100755 index 0000000..4e16a64 --- /dev/null +++ b/playground/generate-output.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +. $(dirname -- "$0")/env.sh + +exec $PYTHON -m opcut generate-output \ + --output-type pdf \ + < $RUN_PATH/result.json \ + > $RUN_PATH/output.pdf \ + diff --git a/playground/generate_output.sh b/playground/generate_output.sh deleted file mode 100755 index 0139233..0000000 --- a/playground/generate_output.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh - -. $(dirname -- "$0")/env.sh - -exec $PYTHON -m opcut generate_output \ - --output-type pdf \ - --result $RUN_PATH/result.json \ - --output $RUN_PATH/output.pdf \ - "$@" diff --git a/schemas/openapi.yaml b/schemas/openapi.yaml index 0a45bb7..442f685 100644 --- a/schemas/openapi.yaml +++ b/schemas/openapi.yaml @@ -7,6 +7,11 @@ paths: '/calculate': post: parameters: + - name: native + in: query + required: false + schema: + type: boolean - name: method in: query required: true diff --git a/src_c/common.h b/src_c/common.h index 22e6a88..5f0b976 100644 --- a/src_c/common.h +++ b/src_c/common.h @@ -8,7 +8,7 @@ #define OPCUT_SUCCESS 0 #define OPCUT_ERROR 1 -#define OPCUT_UNSOLVABLE 2 +#define OPCUT_UNSOLVABLE 42 #ifdef __cplusplus extern "C" { diff --git a/src_js/common.js b/src_js/common.js index a5edfbf..7d4213c 100644 --- a/src_js/common.js +++ b/src_js/common.js @@ -20,8 +20,9 @@ export async function calculate() { r.set('calculating', true); try { const method = r.get('form', 'method'); + const native = r.get('form', 'native'); const params = createCalculateParams(); - const res = await fetch(`${calculateUrl}?method=${method}`, { + const res = await fetch(`${calculateUrl}?method=${method}&native=${native}`, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(params) @@ -159,7 +160,7 @@ function createCalculateParams() { } items[name] = { width: item.width, - height: item.width, + height: item.height, can_rotate: item.can_rotate }; } diff --git a/src_js/dragger.js b/src_js/dragger.js index e591be6..78a59d5 100644 --- a/src_js/dragger.js +++ b/src_js/dragger.js @@ -23,6 +23,6 @@ document.addEventListener('mousemove', evt => { }); -document.addEventListener('mouseup', evt => { +document.addEventListener('mouseup', _ => { draggers.splice(0); }); diff --git a/src_js/states.js b/src_js/states.js index bf3584e..efc31b9 100644 --- a/src_js/states.js +++ b/src_js/states.js @@ -4,6 +4,7 @@ export const main = { method: 'forward_greedy', cut_width: 0.3, min_initial_usage: false, + native: false, panels: [], items: [] }, diff --git a/src_js/vt.js b/src_js/vt.js index ce16d3d..878280f 100644 --- a/src_js/vt.js +++ b/src_js/vt.js @@ -3,7 +3,6 @@ import * as u from '@hat-open/util'; import * as common from './common'; import * as dragger from './dragger'; -import * as states from './states'; export function main() { @@ -23,7 +22,7 @@ function leftPanelResizer() { mousedown: dragger.mouseDownHandler(evt => { const panel = evt.target.parentNode.querySelector('.left-panel'); const width = panel.clientWidth; - return (_, dx, __) => { + return (_, dx) => { panel.style.width = `${width + dx}px`; }; }) @@ -38,7 +37,7 @@ function rightPanelResizer() { mousedown: dragger.mouseDownHandler(evt => { const panel = evt.target.parentNode.querySelector('.right-panel'); const width = panel.clientWidth; - return (_, dx, __) => { + return (_, dx) => { panel.style.width = `${width - dx}px`; }; }) @@ -72,7 +71,11 @@ function leftPanel() { ['label'], checkboxInput('Minimize initial panel usage', r.get('form', 'min_initial_usage'), - val => r.set(['form', 'min_initial_usage'], val)) + val => r.set(['form', 'min_initial_usage'], val)), + ['label'], + checkboxInput('Use native implementation', + r.get('form', 'native'), + val => r.set(['form', 'native'], val)) ], ['div.content', leftPanelPanels(), @@ -99,7 +102,7 @@ function leftPanelPanels() { const valid = !panelNames.has(name); panelNames.add(name); return valid; - } + }; return ['div', ['table', @@ -198,7 +201,7 @@ function leftPanelItems() { const valid = !itemNames.has(name); itemNames.add(name); return valid; - } + }; return ['div', ['table', diff --git a/src_py/opcut/main.py b/src_py/opcut/main.py index df9987f..475288e 100644 --- a/src_py/opcut/main.py +++ b/src_py/opcut/main.py @@ -15,25 +15,13 @@ import opcut.output import opcut.server -log_schema_id: str = 'hat-json://logging.yaml#' params_schema_id: str = 'opcut://opcut.yaml#/definitions/params' result_schema_id: str = 'opcut://opcut.yaml#/definitions/result' @click.group() -@click.option('--log', - default=None, - metavar='PATH', - type=Path, - help=f"logging configuration file path {log_schema_id}") -def main(log: typing.Optional[Path]): +def main(): """Application main entry point""" - if not log: - return - - log_conf = json.decode_file(log) - common.json_schema_repo.validate(log_schema_id, log_conf) - logging.config.dictConfig(log_conf) @main.command() @@ -41,30 +29,35 @@ def main(log: typing.Optional[Path]): default=common.Method.FORWARD_GREEDY, type=common.Method, help="calculate method") -@click.option('--params', - default=None, - metavar='PATH', - type=Path, - help=f"calculate parameters file path ({params_schema_id})") -@click.option('--result', +@click.option('--output', default=None, metavar='PATH', type=Path, help=f"result file path ({result_schema_id})") +@click.argument('params', + required=False, + default=None, + metavar='PATH', + type=Path) def calculate(method: common.Method, - params: typing.Optional[Path], - result: typing.Optional[Path]): + output: typing.Optional[Path], + params: typing.Optional[Path]): """Calculate result""" - params = (json.decode_file(params) if params + params = (json.decode_file(params) if params and params != Path('-') else json.decode_stream(sys.stdin)) common.json_schema_repo.validate(params_schema_id, params) params = common.params_from_json(params) - res = opcut.csp.calculate(params, method) + try: + res = opcut.csp.calculate(params, method) + + except common.UnresolvableError: + sys.exit(42) + res = common.result_to_json(res) - if result: - json.encode_file(res, result) + if output and output != Path('-'): + json.encode_file(res, output) else: json.encode_stream(res, sys.stdout) @@ -77,32 +70,33 @@ def calculate(method: common.Method, @click.option('--panel', default=None, help="panel identifier") -@click.option('--result', - default=None, - metavar='PATH', - type=Path, - help=f"result file path ({result_schema_id})") @click.option('--output', default=None, metavar='PATH', type=Path, help="result file path") +@click.argument('result', + required=False, + default=None, + metavar='PATH', + type=Path) def generate_output(output_type: common.OutputType, panel: typing.Optional[str], - result: typing.Optional[Path], - output: typing.Optional[Path]): + output: typing.Optional[Path], + result: typing.Optional[Path]): """Generate output""" - result = (json.decode_file(result) if result + result = (json.decode_file(result) if result and result != Path('-') else json.decode_stream(sys.stdin)) common.json_schema_repo.validate(result_schema_id, result) result = common.result_from_json(result) out = opcut.output.generate_output(result, output_type, panel) - if output: - out.write_bytes(out) + if output and output != Path('-'): + output.write_bytes(out) else: - sys.stdout.detach().write(out) + stdout, sys.stdout = sys.stdout.detach(), None + stdout.write(out) @main.command() @@ -113,10 +107,29 @@ def generate_output(output_type: common.OutputType, default=8080, type=int, help="listening TCP port") +@click.option('--log-level', + default='INFO', + type=click.Choice(['CRITICAL', 'ERROR', 'WARNING', 'INFO', + 'DEBUG', 'NOTSET']), + help="log level") def server(host: str, - port: int): + port: int, + log_level: str): """Run server""" - loop = aio.init_asyncio() + logging.config.dictConfig({ + 'version': 1, + 'formatters': { + 'console': { + 'format': "[%(asctime)s %(levelname)s %(name)s] %(message)s"}}, + 'handlers': { + 'console': { + 'class': 'logging.StreamHandler', + 'formatter': 'console', + 'level': log_level}}, + 'root': { + 'level': log_level, + 'handlers': ['console']}, + 'disable_existing_loggers': False}) async def run(): server = await opcut.server.create(host, port) @@ -127,6 +140,7 @@ def server(host: str, finally: await aio.uncancellable(server.async_close()) + loop = aio.init_asyncio() with contextlib.suppress(asyncio.CancelledError): aio.run_asyncio(run(), loop=loop) diff --git a/src_py/opcut/server.py b/src_py/opcut/server.py index 92a4186..5e726ad 100644 --- a/src_py/opcut/server.py +++ b/src_py/opcut/server.py @@ -1,11 +1,13 @@ from pathlib import Path +import asyncio +import subprocess +import sys from hat import aio +from hat import json import aiohttp.web from opcut import common -import opcut.csp -import opcut.output static_dir: Path = common.package_path / 'ui' @@ -63,12 +65,13 @@ class Server(aio.Resource): return aiohttp.web.Response(status=400, text="Invalid request") + native = request.query.get('native') == 'true' method = common.Method(request.query['method']) params = common.params_from_json(data) try: - result = await self._executor(opcut.csp.calculate, params, method) - return aiohttp.web.json_response(common.result_to_json(result)) + result = await _calculate(native, method, params) + return aiohttp.web.json_response(result) except common.UnresolvableError: return aiohttp.web.Response(status=400, @@ -88,8 +91,7 @@ class Server(aio.Resource): panel = request.query.get('panel') result = common.result_from_json(data) - output = await self._executor(opcut.output.generate_output, result, - output_type, panel) + output = await _generate_output(output_type, panel, result) if output_type == common.OutputType.PDF: content_type = 'application/pdf' @@ -102,3 +104,53 @@ class Server(aio.Resource): return aiohttp.web.Response(body=output, content_type=content_type) + + +async def _calculate(native, method, params): + args = [*_get_calculate_cmd(native), '--method', method.value] + stdint_data = json.encode(common.params_to_json(params)).encode('utf-8') + + p = await asyncio.create_subprocess_exec(*args, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout_data, stderr_data = await p.communicate(stdint_data) + + if p.returncode == 0: + return json.decode(stdout_data.decode('utf-8')) + + if p.returncode == 42: + raise common.UnresolvableError() + + raise Exception(stderr_data.decode('utf-8')) + + +async def _generate_output(output_type, panel, result): + args = [*_get_generate_output_cmd(), '--output-type', output_type.value, + *(['--panel', panel] if panel else [])] + stdint_data = json.encode(common.result_to_json(result)).encode('utf-8') + + p = await asyncio.create_subprocess_exec(*args, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout_data, stderr_data = await p.communicate(stdint_data) + + if p.returncode == 0: + return stdout_data + + raise Exception(stderr_data.decode('utf-8')) + + +def _get_calculate_cmd(native): + if native and sys.platform == 'linux': + return [str(common.package_path / 'bin/linux-opcut-calculate')] + + elif native and sys.platform == 'win32': + return [str(common.package_path / 'bin/windows-opcut-calculate.exe')] + + return [sys.executable, '-m', 'opcut', 'calculate'] + + +def _get_generate_output_cmd(): + return [sys.executable, '-m', 'opcut', 'generate-output'] -- cgit v1.2.3-70-g09d2