# Advanced

## Custom type converters

You can teach r_bridge how to serialise your own types to R and back, without
modifying the library.

### Serialiser (Python → R)

```python
from r_bridge import register_serializer

class Money:
    def __init__(self, amount: float, currency: str):
        self.amount = amount
        self.currency = currency

register_serializer(
    Money,
    lambda obj: {
        "__type__": "money",
        "amount": obj.amount,
        "currency": obj.currency,
    }
)
```

R receives a named list with the `__type__` field, which you can handle on the
R side however you like.

### Deserialiser (R → Python)

```python
from r_bridge import register_deserializer

register_deserializer(
    "money",
    lambda d: Money(amount=d["amount"], currency=d["currency"])
)
```

Now any R value tagged `{"__type__": "money", ...}` is automatically converted
back to a `Money` instance.

---

## Accessing R libraries

### Using a custom library path

```python
with RBridge(r_libs=["/path/to/my/r/libs"]) as r:
    r.eval('library(mypackage)')
```

The path is prepended to `R_LIBS_USER` before R starts.

### Installing R packages at runtime

```python
with RBridge() as r:
    r.eval('if (!requireNamespace("ggplot2")) install.packages("ggplot2")')
    r.eval('library(ggplot2)')
```

---

## Concurrent usage

`RBridge` is **thread-safe**: a single `threading.Lock` serialises all calls.
Only one request is in flight at a time, so calls from multiple threads are
queued rather than interleaved.

```python
import threading
from r_bridge import RBridge

with RBridge() as r:
    def worker(n):
        result = r.call("sqrt", float(n), result_type_hint="scalar")
        print(f"sqrt({n}) = {result}")

    threads = [threading.Thread(target=worker, args=(i,)) for i in range(10)]
    for t in threads:
        t.start()
    for t in threads:
        t.join()
```

---

## Per-call timeouts

The bridge-wide `call_timeout` can be overridden for individual slow calls:

```python
with RBridge(call_timeout=5.0) as r:
    # most calls use the 5-second default
    r.mean([1, 2, 3])

    # this specific call is allowed 120 seconds
    r.eval("run_long_simulation()", timeout=120.0)
```

---

## Logging

r_bridge uses Python's standard `logging` module under the logger name
`r_bridge`. Set `log_level="DEBUG"` to see non-protocol R stdout and internal
diagnostics:

```python
import logging
logging.basicConfig(level=logging.DEBUG)

with RBridge(log_level="DEBUG") as r:
    r.eval('cat("stray output\n")')
    # logged at DEBUG: R stdout (non-protocol): stray output
```
