# ALASKA v2

**A**dvanced **L**ightweight **A**synchronous **S**ervice **K**ernel for **A**pplications

[![PyPI version](https://badge.fury.io/py/py-alaska.svg)](https://badge.fury.io/py/py-alaska)
[![Python](https://img.shields.io/pypi/pyversions/alaska.svg)](https://pypi.org/project/py-alaska/)
[![License](https://img.shields.io/badge/License-All%20Rights%20Reserved-red.svg)](#license)

Python 멀티프로세스 태스크 관리 프레임워크.
RMI(Remote Method Invocation), 공유 메모리(SmBlock), Signal, DeviceProperty 기반의 산업용 제어 시스템 구축을 지원합니다.

## Features

- **`@task` 데코레이터**: 클래스 하나로 process/thread 태스크 정의
- **RMI**: 프로세스 간 메서드/프로퍼티 호출을 투명하게 처리
- **SmBlock**: 공유 메모리 기반 Zero-copy 데이터 전송 (numpy 배열)
- **Signal**: 계층적 Pub/Sub 메시징 (`signal.camera.connected.emit()`)
- **DeviceProperty**: 선언형 HW 속성 관리 (캐시, opstate, resync, debounce)
- **Qt 통합**: `AlaskaApp.run()`으로 PySide6 UI 태스크 지원
- **Web 모니터링**: HTTP 기반 실시간 대시보드 (태스크 상태, RMI 통계, CPU/메모리)
- **Auto-restart**: 태스크 장애 시 자동 복구
- **JSON 설정**: `@import` 기반 태스크 클래스 자동 탐지 및 속성 주입

## Installation

```bash
pip install py-alaska
```

## Quick Start

### 1. 태스크 정의

```python
from py_alaska import task

@task(mode="process", restart=True)
class MyWorker:
    def __init__(self):
        self.counter = 0

    def increment(self, value: int) -> int:
        self.counter += value
        return self.counter

    def run(self):
        while self.running:
            pass
```

### 2. 설정 파일 (config.json)

```json
{
  "app_info": {
    "name": "MyApp",
    "id": "myapp_001",
    "version": "1.0.0"
  },
  "task_config": {
    "worker/MyWorker": {
      "@import": "my_tasks"
    }
  }
}
```

### 3. 실행

```python
from py_alaska import TaskManager, gconfig

gconfig.load("config.json")
manager = TaskManager(gconfig)
manager.start_all()

client = manager.get_client("worker")
result = client.increment(10)   # RMI: 프로세스 간 호출
print(f"Counter: {result}")     # → Counter: 10
```

## Architecture

```
┌──────────────────────────────────────────────────────────┐
│                      TaskManager                         │
├──────────────────────────────────────────────────────────┤
│  ┌────────────┐  ┌────────────┐  ┌────────────┐         │
│  │  Task A    │  │  Task B    │  │  Task C    │         │
│  │ (Process)  │  │ (Process)  │  │ (Thread)   │         │
│  │            │  │ DEVICE_    │  │  QWidget   │         │
│  │            │  │ PROPERTY   │  │            │         │
│  └─────┬──────┘  └─────┬──────┘  └─────┬──────┘         │
│        │               │               │                │
│        └───────────────┼───────────────┘                │
│                        │                                │
│               ┌────────┴────────┐                       │
│               │  RMI Bus (Queue) │                       │
│               └────────┬────────┘                       │
│                        │                                │
│          ┌─────────────┼─────────────┐                  │
│          │             │             │                  │
│     ┌────┴────┐  ┌─────┴─────┐  ┌───┴────┐             │
│     │ Signal  │  │  SmBlock  │  │ GConfig │             │
│     │ Broker  │  │ (Shared)  │  │ (JSON)  │             │
│     └─────────┘  └───────────┘  └────────┘             │
├──────────────────────────────────────────────────────────┤
│  TaskMonitor (HTTP :7000)  │  TaskProfiler              │
└──────────────────────────────────────────────────────────┘
```

## Core API

### @task 데코레이터

```python
@task(
    name="task_name",      # 태스크 식별자 (생략 시 클래스명 사용)
    mode="process",        # "process" | "thread"
    restart=True,          # 장애 시 자동 재시작
)
class MyTask:
    def run(self):         # 메인 실행 루프
        while self.running:
            pass
```

- 모든 public 메서드가 RMI 대상이 됨
- `self.running`: 실행 상태 플래그
- `self.runtime`: 프레임워크 런타임 (signal, log 등)
- `self.signal`: Signal 클라이언트

### RMI (Remote Method Invocation)

```python
# 태스크 내부에서 다른 태스크 호출
client = self.runtime.get_client("other_task")
result = client.some_method(arg1, arg2)

# 프로퍼티도 RMI로 투명하게 접근
client.exposure = 15000       # setter 호출
value = client.exposure       # getter 호출
```

### Signal

```python
# 발행
self.signal.camera.connected.emit({"source": "cam1", "fps": 30})

# 수신 (on_시그널경로 메서드 자동 바인딩)
def on_camera_connected(self, signal):
    print(signal.data["fps"])
```

### SmBlock (공유 메모리)

```json
{
  "platform_config": {
    "_smblock": {
      "image_pool": {"shape": [1024, 1024, 3], "maxsize": 100}
    }
  }
}
```

```python
index = self.smblock.alloc()            # 블록 할당
buffer = self.smblock.get_buffer(index)  # numpy 배열 참조
buffer[:] = frame_data                   # Zero-copy 쓰기
self.smblock.mfree(index)               # 블록 해제
```

### DeviceProperty

HW 속성을 선언형으로 관리합니다. 캐시, 타입 변환, opstate 조건부 HW 쓰기, resync 일괄 적용을 자동 처리합니다.

```python
@task(mode="process", restart=True)
class CameraDriver:
    DEVICE_PROPERTY = {
        "is_connect:bool=false": {},
        "exposure:int=15000": {
            "setter": "_hw_set_exposure",
            "debounce": 0.5,
        },
        "trigger_mode:bool=false": {
            "setter": "_hw_set_trigger",
        },
        "@resync": {
            "open": "_session_open",
            "close": "_session_close",
            "condition": {"Eq": ["is_connect", True]},
            "order": ["trigger_mode", "exposure"]
        }
    }

    def _hw_set_exposure(self, value):
        ...  # HW에 값 적용

    def _session_open(self):
        ...  # resync 전 HW 잠금

    def _session_close(self):
        ...  # resync 후 HW 해제
```

**동작 흐름:**

```
비연결 상태:  cam.exposure = 1000  → 캐시 저장, Signal 발행, HW 미적용 (opstate 미충족)
연결 시:      cam.is_connect = True → @resync 트리거
              → _session_open()
              → trigger_mode HW 적용
              → exposure HW 적용
              → _session_close()
```

**키 포맷:** `"name:dtype=default"` (예: `"exposure:int=15000"`)

| 옵션 | 설명 |
|------|------|
| `setter` | HW 쓰기 콜백 `(self, value)` |
| `getter` | HW 읽기 콜백 `(self) → value` |
| `validator` | 값 검증 콜백 `(self, value) → value` |
| `debounce` | HW 쓰기 지연 (초). 캐시/Signal은 즉시, HW는 타이머 리셋 후 적용 |
| `notify_mode` | `"immediate"` (기본) 또는 `"on_write"` |
| `@resync` | `{open, close, condition, order}` — 조건 충족 시 일괄 적용 |

## Config (JSON)

```json
{
  "app_info": {"name": "MyApp", "id": "node-001", "version": "1.0.0"},
  "platform_config": {
    "_smblock": {"pool": {"shape": [1024, 1024, 3], "maxsize": 100}},
    "_monitor": {"port": 7000, "exit_hook": true}
  },
  "task_config": {
    "group/task_name": {
      "@import": "module.path",
      "property1": "value1"
    }
  }
}
```

- `@import`: 태스크 클래스가 위치한 모듈 경로
- `group/task_name`: `group`은 논리 그룹, `task_name`은 `@task(name=...)` 이름
- 나머지 키-값: 태스크 인스턴스 속성으로 자동 주입
- `client:task_id`: RMI 프록시 자동 주입 (`self.target = client:camera1`)
- `smblock:pool_name`: SmBlock 인스턴스 자동 주입

## Examples

| 예제 | 설명 |
|------|------|
| `ex010_hello_world` | 최소 태스크 정의 및 실행 |
| `ex020_debug` | 디버그 모드 |
| `ex030_profiler` | 태스크 프로파일러 |
| `ex040_restart` | 장애 시 자동 재시작 |
| `ex050_select_signal` | Signal 선택적 구독 (Producer-Worker) |
| `ex060_dynamic_signal` | 동적 Signal 생성 |
| `ex070_chain_call` | RMI 체인 호출 |
| `ex080_persistent` | 영구 설정 관리 (GConfig) |
| `ex091_property_rmi` | 프로퍼티 RMI 접근 (Process/Thread) |
| `ex100_direct_update_ui` | Qt UI 직접 업데이트 |
| `ex110_smblock` | SmBlock 공유 메모리 IPC |
| `ex120_task` | 태스크 고급 패턴 |
| `ex130_camera` | IMI 카메라 뷰어 (CamProperty) |
| `ex130a_camera` | IMI 카메라 뷰어 (DeviceProperty) |
| `ex140_performance` | 성능 측정 |
| `ex150_dynamic_task` | 동적 태스크 생성/제거 |
| `ex160_device_property` | DeviceProperty 데모 |

## Monitoring

`http://localhost:7000` (설정 포트)에서 웹 대시보드에 접근합니다.

- 태스크 상태 (alive/stopped)
- RMI 호출 통계 (횟수, 응답시간)
- CPU/메모리 사용량
- SmBlock 풀 사용률
- 설정 편집기

## Requirements

- Python >= 3.8
- numpy
- opencv-python (optional)
- PySide6 (optional, Qt UI)

## License

Copyright (c) 동일비전(Dongil Vision Korea). All Rights Reserved.
무단 복제 및 배포를 금지합니다. 연구 목적의 개인 사용은 허용됩니다.
