aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbozo.kopic <bozo@kopic.xyz>2022-01-03 00:58:17 +0100
committerbozo.kopic <bozo@kopic.xyz>2022-01-03 00:58:17 +0100
commitae33b2c75ec0e03fd1d80241a4c4b29acadb517c (patch)
treefa86a159bafb6ea5c1605cd66a67f8be5a24ba3e
parent1a73bd946b8636dbcbb2970c8f8e919b888074ab (diff)
calculate native implementation
-rwxr-xr-xplayground/calculate.sh6
-rwxr-xr-xplayground/generate-output.sh9
-rwxr-xr-xplayground/generate_output.sh9
-rw-r--r--schemas/openapi.yaml5
-rw-r--r--src_c/common.h2
-rw-r--r--src_js/common.js5
-rw-r--r--src_js/dragger.js2
-rw-r--r--src_js/states.js1
-rw-r--r--src_js/vt.js15
-rw-r--r--src_py/opcut/main.py90
-rw-r--r--src_py/opcut/server.py64
11 files changed, 142 insertions, 66 deletions
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']