fix(fn-run): propagar stdout/stderr de bash functions library-style #1
@@ -0,0 +1,136 @@
|
||||
#include "core/parallel_for.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <exception>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace fn {
|
||||
|
||||
struct ThreadPool::Impl {
|
||||
std::vector<std::thread> workers;
|
||||
std::queue<std::function<void()>> jobs;
|
||||
std::mutex m;
|
||||
std::condition_variable cv;
|
||||
bool stop = false;
|
||||
|
||||
int n_threads = 0;
|
||||
|
||||
// Estado por parallel_for:
|
||||
std::atomic<int> active_jobs{0};
|
||||
std::mutex done_m;
|
||||
std::condition_variable done_cv;
|
||||
std::exception_ptr first_exc;
|
||||
std::mutex exc_m;
|
||||
|
||||
void worker_loop() {
|
||||
for (;;) {
|
||||
std::function<void()> job;
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m);
|
||||
cv.wait(lk, [this] { return stop || !jobs.empty(); });
|
||||
if (stop && jobs.empty()) return;
|
||||
job = std::move(jobs.front());
|
||||
jobs.pop();
|
||||
}
|
||||
try {
|
||||
job();
|
||||
} catch (...) {
|
||||
std::lock_guard<std::mutex> lk(exc_m);
|
||||
if (!first_exc) first_exc = std::current_exception();
|
||||
}
|
||||
if (active_jobs.fetch_sub(1) == 1) {
|
||||
std::lock_guard<std::mutex> lk(done_m);
|
||||
done_cv.notify_all();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ThreadPool::ThreadPool(int n_threads) : impl_(new Impl) {
|
||||
if (n_threads <= 0) {
|
||||
n_threads = static_cast<int>(std::thread::hardware_concurrency());
|
||||
if (n_threads <= 0) n_threads = 1;
|
||||
}
|
||||
impl_->n_threads = n_threads;
|
||||
impl_->workers.reserve(static_cast<std::size_t>(n_threads));
|
||||
for (int i = 0; i < n_threads; ++i) {
|
||||
impl_->workers.emplace_back([this] { impl_->worker_loop(); });
|
||||
}
|
||||
}
|
||||
|
||||
ThreadPool::~ThreadPool() {
|
||||
if (!impl_) return;
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(impl_->m);
|
||||
impl_->stop = true;
|
||||
}
|
||||
impl_->cv.notify_all();
|
||||
for (auto& t : impl_->workers) {
|
||||
if (t.joinable()) t.join();
|
||||
}
|
||||
delete impl_;
|
||||
impl_ = nullptr;
|
||||
}
|
||||
|
||||
int ThreadPool::n_threads() const { return impl_->n_threads; }
|
||||
|
||||
void ThreadPool::parallel_for_chunks(std::size_t begin, std::size_t end,
|
||||
const std::function<void(int, std::size_t, std::size_t)>& fn) {
|
||||
if (end <= begin) return;
|
||||
int N = impl_->n_threads;
|
||||
std::size_t total = end - begin;
|
||||
std::size_t chunk = (total + static_cast<std::size_t>(N) - 1) /
|
||||
static_cast<std::size_t>(N);
|
||||
int actual_chunks = 0;
|
||||
for (int t = 0; t < N; ++t) {
|
||||
std::size_t lo = begin + chunk * static_cast<std::size_t>(t);
|
||||
if (lo >= end) break;
|
||||
std::size_t hi = lo + chunk;
|
||||
if (hi > end) hi = end;
|
||||
++actual_chunks;
|
||||
}
|
||||
if (actual_chunks == 0) return;
|
||||
|
||||
impl_->first_exc = nullptr;
|
||||
impl_->active_jobs.store(actual_chunks);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(impl_->m);
|
||||
for (int t = 0; t < N; ++t) {
|
||||
std::size_t lo = begin + chunk * static_cast<std::size_t>(t);
|
||||
if (lo >= end) break;
|
||||
std::size_t hi = lo + chunk;
|
||||
if (hi > end) hi = end;
|
||||
int tid = t;
|
||||
impl_->jobs.emplace([&fn, tid, lo, hi] { fn(tid, lo, hi); });
|
||||
}
|
||||
}
|
||||
impl_->cv.notify_all();
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(impl_->done_m);
|
||||
impl_->done_cv.wait(lk, [this] {
|
||||
return impl_->active_jobs.load() == 0;
|
||||
});
|
||||
}
|
||||
|
||||
if (impl_->first_exc) {
|
||||
std::exception_ptr e = impl_->first_exc;
|
||||
impl_->first_exc = nullptr;
|
||||
std::rethrow_exception(e);
|
||||
}
|
||||
}
|
||||
|
||||
void ThreadPool::parallel_for(std::size_t begin, std::size_t end,
|
||||
const std::function<void(std::size_t)>& fn) {
|
||||
parallel_for_chunks(begin, end,
|
||||
[&fn](int /*tid*/, std::size_t lo, std::size_t hi) {
|
||||
for (std::size_t i = lo; i < hi; ++i) fn(i);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace fn
|
||||
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
|
||||
namespace fn {
|
||||
|
||||
// Pool de threads opaco. Crea N workers en el constructor (default = hw
|
||||
// concurrency); el destructor los joinea. Re-utilizable across multiples
|
||||
// parallel_for; cada llamada bloquea hasta que todos los hilos terminan
|
||||
// el rango.
|
||||
class ThreadPool {
|
||||
public:
|
||||
explicit ThreadPool(int n_threads = 0); // 0 = std::thread::hardware_concurrency()
|
||||
~ThreadPool();
|
||||
|
||||
ThreadPool(const ThreadPool&) = delete;
|
||||
ThreadPool& operator=(const ThreadPool&) = delete;
|
||||
|
||||
int n_threads() const;
|
||||
|
||||
// Ejecuta fn(i) para i en [begin, end), repartido entre los workers
|
||||
// en chunks contiguos. Bloquea hasta finalizar.
|
||||
//
|
||||
// Si una excepcion se lanza en algun worker, parallel_for la captura
|
||||
// y la relanza en el thread caller tras el join (la primera).
|
||||
void parallel_for(std::size_t begin, std::size_t end,
|
||||
const std::function<void(std::size_t)>& fn);
|
||||
|
||||
// Ejecuta fn(thread_id, begin, end) por chunk — util cuando cada thread
|
||||
// necesita acumular state local. fn recibe los limites del chunk.
|
||||
void parallel_for_chunks(std::size_t begin, std::size_t end,
|
||||
const std::function<void(int /*tid*/,
|
||||
std::size_t /*lo*/,
|
||||
std::size_t /*hi*/)>& fn);
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
Impl* impl_;
|
||||
};
|
||||
|
||||
} // namespace fn
|
||||
@@ -0,0 +1,74 @@
|
||||
---
|
||||
name: parallel_for
|
||||
kind: function
|
||||
lang: cpp
|
||||
domain: core
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "ThreadPool::ThreadPool(int n_threads = 0); void ThreadPool::parallel_for(size_t begin, size_t end, fn(i)); void ThreadPool::parallel_for_chunks(size_t begin, size_t end, fn(tid, lo, hi))"
|
||||
description: "Pool de threads reutilizable con parallel_for por indice y parallel_for_chunks para acumulado por thread. Reparte rango contiguo por hw concurrency. Captura excepciones del worker y las relanza en el caller. Para Monte Carlo CPU multi-core."
|
||||
tags: [thread, parallel, concurrency, montecarlo, core]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: [thread, mutex, condition_variable, atomic, queue, vector, functional, exception]
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "cpp/functions/core/parallel_for.cpp"
|
||||
params:
|
||||
- name: n_threads
|
||||
desc: "Numero de workers. 0 = hardware_concurrency. El destructor joinea todos."
|
||||
- name: begin
|
||||
desc: "Indice inicial del rango (inclusive)."
|
||||
- name: end
|
||||
desc: "Indice final del rango (exclusive). end <= begin es no-op."
|
||||
- name: fn
|
||||
desc: "Functor a ejecutar. parallel_for: void(size_t i). parallel_for_chunks: void(int tid, size_t lo, size_t hi) — el chunk [lo, hi) lo procesa el thread tid."
|
||||
output: "El pool ejecuta el functor en paralelo y bloquea el caller hasta que todos los chunks terminan. Si algun worker lanza una excepcion, se captura la primera y se relanza desde el caller tras el join."
|
||||
---
|
||||
|
||||
# parallel_for
|
||||
|
||||
Pool minimo para Monte Carlo CPU. Una instancia mantiene workers vivos entre llamadas — barato hacer muchas N pequenas (no hay overhead de spawn por llamada).
|
||||
|
||||
## Patron tipico — N sesiones independientes
|
||||
|
||||
```cpp
|
||||
fn::ThreadPool pool; // hardware_concurrency workers
|
||||
|
||||
std::vector<double> pnl(N);
|
||||
pool.parallel_for(0, N, [&](std::size_t i) {
|
||||
fn::ds::Rng r;
|
||||
fn::ds::rng_seed(r, /*master=*/0xC0FFEE ^ i); // seed por sesion
|
||||
pnl[i] = simulate_session(r);
|
||||
});
|
||||
```
|
||||
|
||||
## Patron — accumulado por thread (sum / hist local)
|
||||
|
||||
`parallel_for_chunks` evita atomic contention dando un slice contiguo a cada thread:
|
||||
|
||||
```cpp
|
||||
std::vector<double> partials(pool.n_threads(), 0.0);
|
||||
|
||||
pool.parallel_for_chunks(0, N,
|
||||
[&](int tid, std::size_t lo, std::size_t hi) {
|
||||
double local = 0.0;
|
||||
for (std::size_t i = lo; i < hi; ++i) {
|
||||
local += compute(i);
|
||||
}
|
||||
partials[tid] = local;
|
||||
});
|
||||
|
||||
double total = std::accumulate(partials.begin(), partials.end(), 0.0);
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
- **Throughput**: 8 cores @ 4 GHz, jobs de ~10us cada uno => ~5x speedup tipico (Amdahl: lo bloquea el job mas largo + cv-wait coste). Para jobs <1us el coste de submit domina; usar `parallel_for_chunks` con chunk grande.
|
||||
- **Excepciones**: solo se preserva la PRIMERA excepcion. Las demas se silencian (con la garantia de que el resto de los workers terminan limpiamente).
|
||||
- **No re-entrante**: un worker no puede llamar `parallel_for` sobre el mismo pool — bloquearia el done_cv. Si se necesita anidamiento, crear un segundo pool.
|
||||
- **Comparar con GPU**: para 10^6 sesiones de 10^4 spins (= 10^10 ops MC), CPU 8-core ~10s, GPU ~1-2s. CPU pool es la fallback portable; GPU es para el caso extremo.
|
||||
@@ -0,0 +1,80 @@
|
||||
#include "core/slider.h"
|
||||
#include "core/tokens.h"
|
||||
#include <imgui.h>
|
||||
#include <cstdio>
|
||||
|
||||
namespace fn_ui {
|
||||
|
||||
static void push_slider_style() {
|
||||
using namespace fn_tokens;
|
||||
ImGui::PushStyleColor(ImGuiCol_FrameBg, colors::bg);
|
||||
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, colors::surface_hover);
|
||||
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, colors::surface);
|
||||
ImGui::PushStyleColor(ImGuiCol_Border, colors::border);
|
||||
ImGui::PushStyleColor(ImGuiCol_SliderGrab, colors::primary);
|
||||
ImGui::PushStyleColor(ImGuiCol_SliderGrabActive, colors::primary_hover);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, radius::sm);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_GrabRounding, radius::sm);
|
||||
}
|
||||
|
||||
static void pop_slider_style() {
|
||||
ImGui::PopStyleVar(3);
|
||||
ImGui::PopStyleColor(6);
|
||||
}
|
||||
|
||||
static void label_muted(const char* label) {
|
||||
using namespace fn_tokens;
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, colors::text_muted);
|
||||
ImGui::TextUnformatted(label);
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
bool slider_float(const char* label, float* value,
|
||||
float min_v, float max_v, const char* fmt) {
|
||||
label_muted(label);
|
||||
push_slider_style();
|
||||
char id[160];
|
||||
std::snprintf(id, sizeof(id), "##%s", label);
|
||||
ImGui::SetNextItemWidth(-FLT_MIN);
|
||||
bool changed = ImGui::SliderFloat(id, value, min_v, max_v, fmt);
|
||||
pop_slider_style();
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool slider_float_log(const char* label, float* value,
|
||||
float min_v, float max_v, const char* fmt) {
|
||||
label_muted(label);
|
||||
push_slider_style();
|
||||
char id[160];
|
||||
std::snprintf(id, sizeof(id), "##%s", label);
|
||||
ImGui::SetNextItemWidth(-FLT_MIN);
|
||||
bool changed = ImGui::SliderFloat(id, value, min_v, max_v, fmt,
|
||||
ImGuiSliderFlags_Logarithmic);
|
||||
pop_slider_style();
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool slider_int(const char* label, int* value,
|
||||
int min_v, int max_v, const char* fmt) {
|
||||
label_muted(label);
|
||||
push_slider_style();
|
||||
char id[160];
|
||||
std::snprintf(id, sizeof(id), "##%s", label);
|
||||
ImGui::SetNextItemWidth(-FLT_MIN);
|
||||
bool changed = ImGui::SliderInt(id, value, min_v, max_v, fmt);
|
||||
pop_slider_style();
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool slider_double(const char* label, double* value,
|
||||
double min_v, double max_v, const char* fmt) {
|
||||
float fv = static_cast<float>(*value);
|
||||
bool changed = slider_float(label, &fv,
|
||||
static_cast<float>(min_v),
|
||||
static_cast<float>(max_v), fmt);
|
||||
if (changed) *value = static_cast<double>(fv);
|
||||
return changed;
|
||||
}
|
||||
|
||||
} // namespace fn_ui
|
||||
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
// Slider con label muted arriba + value display, estilo acorde con fn_tokens.
|
||||
// Equivalente simple de <Slider> de Mantine / fn_library.
|
||||
//
|
||||
// Uso:
|
||||
// static float p = 0.2f;
|
||||
// fn_ui::slider_float("Probabilidad de pagar", &p, 0.0f, 1.0f);
|
||||
//
|
||||
// static int n = 10;
|
||||
// fn_ui::slider_int("Spins por sesion", &n, 1, 100);
|
||||
|
||||
namespace fn_ui {
|
||||
|
||||
// Renderiza label arriba + slider abajo. fmt es el printf-format que
|
||||
// ImGui usa para el value display (default "%.3f"). Devuelve true si el
|
||||
// valor cambio este frame.
|
||||
bool slider_float(const char* label, float* value,
|
||||
float min_v, float max_v,
|
||||
const char* fmt = "%.3f");
|
||||
|
||||
// Logaritmico: util para parametros que cubren ordenes de magnitud
|
||||
// (sigma, learning rate, ...).
|
||||
bool slider_float_log(const char* label, float* value,
|
||||
float min_v, float max_v,
|
||||
const char* fmt = "%.4f");
|
||||
|
||||
bool slider_int(const char* label, int* value,
|
||||
int min_v, int max_v,
|
||||
const char* fmt = "%d");
|
||||
|
||||
// Variante double: ImGui no tiene SliderDouble, asi que internamente
|
||||
// down-cast a float — ojo si tu rango requiere precision fp64.
|
||||
bool slider_double(const char* label, double* value,
|
||||
double min_v, double max_v,
|
||||
const char* fmt = "%.6f");
|
||||
|
||||
} // namespace fn_ui
|
||||
@@ -0,0 +1,66 @@
|
||||
---
|
||||
name: slider
|
||||
kind: function
|
||||
lang: cpp
|
||||
domain: core
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "bool slider_float(const char* label, float* v, float min, float max, const char* fmt); bool slider_float_log(...); bool slider_int(const char* label, int* v, int min, int max, const char* fmt); bool slider_double(const char* label, double* v, double min, double max, const char* fmt)"
|
||||
description: "Slider ImGui con label muted arriba, estilo acorde con fn_tokens (radius, border, primary grab). Variantes float, float_log (logaritmico), int, double. Equivalente al <Slider> de Mantine / fn_library."
|
||||
tags: [imgui, slider, ui, tokens, mantine, core]
|
||||
uses_functions: ["tokens_cpp_core"]
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: [imgui.h, cstdio]
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "cpp/functions/core/slider.cpp"
|
||||
framework: imgui
|
||||
params:
|
||||
- name: label
|
||||
desc: "Texto del label (mostrado en text_muted arriba). Tambien se usa como id ImGui (concatenado con ##)."
|
||||
- name: value
|
||||
desc: "Pointer al valor; mutado in-place si el usuario lo cambia."
|
||||
- name: min_v
|
||||
desc: "Limite inferior."
|
||||
- name: max_v
|
||||
desc: "Limite superior."
|
||||
- name: fmt
|
||||
desc: "printf-format para el value display. Float default '%.3f'; int default '%d'."
|
||||
output: "Renderiza label + slider full-width. Devuelve true si el valor cambio este frame."
|
||||
---
|
||||
|
||||
# slider
|
||||
|
||||
Slider canonico para los calculadores. Cada calculadora del set tiene 15-30 sliders; tener uno consistente con tokens y full-width acelera el desarrollo y mantiene la identidad visual.
|
||||
|
||||
## Patron tipico
|
||||
|
||||
```cpp
|
||||
static float prob = 0.2f;
|
||||
if (fn_ui::slider_float("Probabilidad de hit", &prob, 0.0f, 1.0f, "%.3f")) {
|
||||
recalculate();
|
||||
}
|
||||
|
||||
static float sigma = 0.1f;
|
||||
fn_ui::slider_float_log("Proposal sigma (log)", &sigma, 0.001f, 10.0f, "%.4f");
|
||||
|
||||
static int n_chains = 4;
|
||||
fn_ui::slider_int("Numero de cadenas", &n_chains, 1, 16);
|
||||
|
||||
static double mu = 0.0;
|
||||
fn_ui::slider_double("Mu", &mu, -10.0, 10.0, "%.6f");
|
||||
```
|
||||
|
||||
## Estilo
|
||||
|
||||
Usa `fn_tokens::colors::primary` para el grab (mismo color que `button` primary). Background `bg`, border `border`, radius `sm`. Coincide con la identidad de `text_input` y demas widgets `core`.
|
||||
|
||||
## Notas
|
||||
|
||||
- `slider_double` down-castea a float internamente (ImGui no expone SliderDouble). Si tu rango requiere fp64 usa `text_input` con parsing manual.
|
||||
- El label es tambien el id ImGui — duplicar labels en el mismo frame causa colision (ImGui asigna ids unicos basados en label). Si necesitas dos sliders con el mismo nombre visible, agregar un sufijo distinto (ej. "Mu##chain1" / "Mu##chain2").
|
||||
- Para multi-value (vec2/vec3) — no incluido aqui; usar 2/3 sliders separados o ImGui::SliderFloat2/3 directamente.
|
||||
Reference in New Issue
Block a user