mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-13 12:08:13 -07:00
traversable_derive: compiles
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
use std::path::Path;
|
||||
|
||||
use brk_cohort::{
|
||||
ByAgeRange, ByAmountRange, ByEpoch, ByGreatEqualAmount, ByLowerThanAmount, ByMaxAge, ByMinAge,
|
||||
ByClass, BySpendableType, CohortContext, Filter, Term,
|
||||
ByAgeRange, ByAmountRange, ByClass, ByEpoch, ByGreatEqualAmount, ByLowerThanAmount, ByMaxAge,
|
||||
ByMinAge, BySpendableType, CohortContext, Filter, Term,
|
||||
};
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
@@ -13,41 +13,32 @@ use vecdb::{AnyStoredVec, Database, Exit, ReadOnlyClone, ReadableVec, Rw, Storag
|
||||
use crate::{blocks, distribution::DynCohortVecs, indexes, prices};
|
||||
|
||||
use crate::distribution::metrics::{
|
||||
AllCohortMetrics, BasicCohortMetrics, CohortMetricsBase,
|
||||
CoreCohortMetrics, ExtendedAdjustedCohortMetrics, ExtendedCohortMetrics, ImportConfig,
|
||||
MinimalCohortMetrics, SupplyMetrics,
|
||||
AllCohortMetrics, BasicCohortMetrics, CohortMetricsBase, CoreCohortMetrics,
|
||||
ExtendedAdjustedCohortMetrics, ExtendedCohortMetrics, ImportConfig, MinimalCohortMetrics,
|
||||
SupplyMetrics,
|
||||
};
|
||||
|
||||
use super::{percentiles::PercentileCache, vecs::UTXOCohortVecs};
|
||||
|
||||
use crate::distribution::state::{CoreRealizedState, MinimalRealizedState, RealizedState, UTXOCohortState};
|
||||
use crate::distribution::state::UTXOCohortState;
|
||||
|
||||
const VERSION: Version = Version::new(0);
|
||||
|
||||
/// All UTXO cohorts organized by filter type.
|
||||
///
|
||||
/// Each group uses a concrete metrics type matching its required features:
|
||||
/// - age_range: extended realized + extended cost basis
|
||||
/// - epoch/class/amount/type: basic metrics with relative
|
||||
/// - all: extended + adjusted (no rel_to_all)
|
||||
/// - sth: extended + adjusted
|
||||
/// - lth: extended
|
||||
/// - max_age: adjusted
|
||||
/// - min_age: basic
|
||||
#[derive(Traversable)]
|
||||
pub struct UTXOCohorts<M: StorageMode = Rw> {
|
||||
pub all: UTXOCohortVecs<AllCohortMetrics<M>, RealizedState>,
|
||||
pub sth: UTXOCohortVecs<ExtendedAdjustedCohortMetrics<M>, RealizedState>,
|
||||
pub lth: UTXOCohortVecs<ExtendedCohortMetrics<M>, RealizedState>,
|
||||
pub age_range: ByAgeRange<UTXOCohortVecs<BasicCohortMetrics<M>, RealizedState>>,
|
||||
pub max_age: ByMaxAge<UTXOCohortVecs<CoreCohortMetrics<M>, CoreRealizedState>>,
|
||||
pub min_age: ByMinAge<UTXOCohortVecs<CoreCohortMetrics<M>, CoreRealizedState>>,
|
||||
pub ge_amount: ByGreatEqualAmount<UTXOCohortVecs<MinimalCohortMetrics<M>, MinimalRealizedState>>,
|
||||
pub amount_range: ByAmountRange<UTXOCohortVecs<MinimalCohortMetrics<M>, MinimalRealizedState>>,
|
||||
pub lt_amount: ByLowerThanAmount<UTXOCohortVecs<MinimalCohortMetrics<M>, MinimalRealizedState>>,
|
||||
pub epoch: ByEpoch<UTXOCohortVecs<CoreCohortMetrics<M>, CoreRealizedState>>,
|
||||
pub class: ByClass<UTXOCohortVecs<CoreCohortMetrics<M>, CoreRealizedState>>,
|
||||
pub type_: BySpendableType<UTXOCohortVecs<MinimalCohortMetrics<M>, MinimalRealizedState>>,
|
||||
pub all: UTXOCohortVecs<AllCohortMetrics<M>>,
|
||||
pub sth: UTXOCohortVecs<ExtendedAdjustedCohortMetrics<M>>,
|
||||
pub lth: UTXOCohortVecs<ExtendedCohortMetrics<M>>,
|
||||
pub age_range: ByAgeRange<UTXOCohortVecs<BasicCohortMetrics<M>>>,
|
||||
pub max_age: ByMaxAge<UTXOCohortVecs<CoreCohortMetrics<M>>>,
|
||||
pub min_age: ByMinAge<UTXOCohortVecs<CoreCohortMetrics<M>>>,
|
||||
pub epoch: ByEpoch<UTXOCohortVecs<CoreCohortMetrics<M>>>,
|
||||
pub class: ByClass<UTXOCohortVecs<CoreCohortMetrics<M>>>,
|
||||
pub ge_amount: ByGreatEqualAmount<UTXOCohortVecs<MinimalCohortMetrics<M>>>,
|
||||
pub amount_range: ByAmountRange<UTXOCohortVecs<MinimalCohortMetrics<M>>>,
|
||||
pub lt_amount: ByLowerThanAmount<UTXOCohortVecs<MinimalCohortMetrics<M>>>,
|
||||
pub type_: BySpendableType<UTXOCohortVecs<MinimalCohortMetrics<M>>>,
|
||||
#[traversable(skip)]
|
||||
pub(super) percentile_cache: PercentileCache,
|
||||
/// Cached partition_point positions for tick_tock boundary searches.
|
||||
@@ -57,7 +48,6 @@ pub struct UTXOCohorts<M: StorageMode = Rw> {
|
||||
pub(super) tick_tock_cached_positions: [usize; 20],
|
||||
}
|
||||
|
||||
|
||||
impl UTXOCohorts<Rw> {
|
||||
/// ~71 separate cohorts (21 age + 5 epoch + 18 class + 15 amount + 12 type)
|
||||
const SEPARATE_COHORT_CAPACITY: usize = 80;
|
||||
@@ -86,7 +76,7 @@ impl UTXOCohorts<Rw> {
|
||||
|
||||
// Helper for separate cohorts with BasicCohortMetrics + full state
|
||||
let basic_separate =
|
||||
|f: Filter, name: &'static str| -> Result<UTXOCohortVecs<BasicCohortMetrics, RealizedState>> {
|
||||
|f: Filter, name: &'static str| -> Result<UTXOCohortVecs<BasicCohortMetrics>> {
|
||||
let full_name = CohortContext::Utxo.full_name(&f, name);
|
||||
let cfg = ImportConfig {
|
||||
db,
|
||||
@@ -105,7 +95,7 @@ impl UTXOCohorts<Rw> {
|
||||
let age_range = ByAgeRange::try_new(&basic_separate)?;
|
||||
|
||||
let core_separate =
|
||||
|f: Filter, name: &'static str| -> Result<UTXOCohortVecs<CoreCohortMetrics, CoreRealizedState>> {
|
||||
|f: Filter, name: &'static str| -> Result<UTXOCohortVecs<CoreCohortMetrics>> {
|
||||
let full_name = CohortContext::Utxo.full_name(&f, name);
|
||||
let cfg = ImportConfig {
|
||||
db,
|
||||
@@ -126,7 +116,7 @@ impl UTXOCohorts<Rw> {
|
||||
|
||||
// Helper for separate cohorts with MinimalCohortMetrics + MinimalRealizedState
|
||||
let minimal_separate =
|
||||
|f: Filter, name: &'static str| -> Result<UTXOCohortVecs<MinimalCohortMetrics, MinimalRealizedState>> {
|
||||
|f: Filter, name: &'static str| -> Result<UTXOCohortVecs<MinimalCohortMetrics>> {
|
||||
let full_name = CohortContext::Utxo.full_name(&f, name);
|
||||
let cfg = ImportConfig {
|
||||
db,
|
||||
@@ -183,7 +173,7 @@ impl UTXOCohorts<Rw> {
|
||||
|
||||
// CoreCohortMetrics without state (no state, for aggregate cohorts)
|
||||
let core_no_state =
|
||||
|f: Filter, name: &'static str| -> Result<UTXOCohortVecs<CoreCohortMetrics, CoreRealizedState>> {
|
||||
|f: Filter, name: &'static str| -> Result<UTXOCohortVecs<CoreCohortMetrics>> {
|
||||
let full_name = CohortContext::Utxo.full_name(&f, name);
|
||||
let cfg = ImportConfig {
|
||||
db,
|
||||
@@ -206,7 +196,7 @@ impl UTXOCohorts<Rw> {
|
||||
|
||||
// MinimalCohortMetrics without state (for aggregate amount cohorts)
|
||||
let minimal_no_state =
|
||||
|f: Filter, name: &'static str| -> Result<UTXOCohortVecs<MinimalCohortMetrics, MinimalRealizedState>> {
|
||||
|f: Filter, name: &'static str| -> Result<UTXOCohortVecs<MinimalCohortMetrics>> {
|
||||
let full_name = CohortContext::Utxo.full_name(&f, name);
|
||||
let cfg = ImportConfig {
|
||||
db,
|
||||
@@ -246,7 +236,12 @@ impl UTXOCohorts<Rw> {
|
||||
&mut self,
|
||||
) -> impl ParallelIterator<Item = &mut dyn DynCohortVecs> {
|
||||
let Self {
|
||||
age_range, epoch, class, amount_range, type_, ..
|
||||
age_range,
|
||||
epoch,
|
||||
class,
|
||||
amount_range,
|
||||
type_,
|
||||
..
|
||||
} = self;
|
||||
age_range
|
||||
.par_iter_mut()
|
||||
@@ -278,8 +273,15 @@ impl UTXOCohorts<Rw> {
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
let Self {
|
||||
all, sth, lth, age_range, max_age, min_age,
|
||||
ge_amount, amount_range, lt_amount,
|
||||
all,
|
||||
sth,
|
||||
lth,
|
||||
age_range,
|
||||
max_age,
|
||||
min_age,
|
||||
ge_amount,
|
||||
amount_range,
|
||||
lt_amount,
|
||||
..
|
||||
} = self;
|
||||
|
||||
@@ -313,10 +315,14 @@ impl UTXOCohorts<Rw> {
|
||||
})
|
||||
}),
|
||||
Box::new(|| {
|
||||
ge_amount.par_iter_mut().chain(lt_amount.par_iter_mut()).try_for_each(|vecs| {
|
||||
let sources = filter_minimal_sources_from(amr.iter(), Some(&vecs.metrics.filter));
|
||||
vecs.metrics.compute_from_sources(si, &sources, exit)
|
||||
})
|
||||
ge_amount
|
||||
.par_iter_mut()
|
||||
.chain(lt_amount.par_iter_mut())
|
||||
.try_for_each(|vecs| {
|
||||
let sources =
|
||||
filter_minimal_sources_from(amr.iter(), Some(&vecs.metrics.filter));
|
||||
vecs.metrics.compute_from_sources(si, &sources, exit)
|
||||
})
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -338,7 +344,8 @@ impl UTXOCohorts<Rw> {
|
||||
) -> Result<()> {
|
||||
// 1. Compute all metrics except net_sentiment (all cohorts via DynCohortVecs)
|
||||
{
|
||||
let mut all: Vec<&mut dyn DynCohortVecs> = Vec::with_capacity(Self::SEPARATE_COHORT_CAPACITY + 3);
|
||||
let mut all: Vec<&mut dyn DynCohortVecs> =
|
||||
Vec::with_capacity(Self::SEPARATE_COHORT_CAPACITY + 3);
|
||||
all.push(&mut self.all);
|
||||
all.push(&mut self.sth);
|
||||
all.push(&mut self.lth);
|
||||
@@ -377,7 +384,10 @@ impl UTXOCohorts<Rw> {
|
||||
// Note: ge_amount, lt_amount, amount_range are Minimal tier — no net_sentiment.
|
||||
{
|
||||
let Self {
|
||||
all, sth, lth, age_range,
|
||||
all,
|
||||
sth,
|
||||
lth,
|
||||
age_range,
|
||||
..
|
||||
} = self;
|
||||
|
||||
@@ -387,15 +397,18 @@ impl UTXOCohorts<Rw> {
|
||||
let tasks: Vec<Box<dyn FnOnce() -> Result<()> + Send + '_>> = vec![
|
||||
Box::new(|| {
|
||||
let sources = filter_sources_from(ar.iter(), None);
|
||||
all.metrics.compute_net_sentiment_from_others(si, &sources, exit)
|
||||
all.metrics
|
||||
.compute_net_sentiment_from_others(si, &sources, exit)
|
||||
}),
|
||||
Box::new(|| {
|
||||
let sources = filter_sources_from(ar.iter(), Some(sth.metrics.filter()));
|
||||
sth.metrics.compute_net_sentiment_from_others(si, &sources, exit)
|
||||
sth.metrics
|
||||
.compute_net_sentiment_from_others(si, &sources, exit)
|
||||
}),
|
||||
Box::new(|| {
|
||||
let sources = filter_sources_from(ar.iter(), Some(lth.metrics.filter()));
|
||||
lth.metrics.compute_net_sentiment_from_others(si, &sources, exit)
|
||||
lth.metrics
|
||||
.compute_net_sentiment_from_others(si, &sources, exit)
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -454,8 +467,18 @@ impl UTXOCohorts<Rw> {
|
||||
|
||||
// Destructure to allow parallel mutable access to independent fields.
|
||||
let Self {
|
||||
sth, lth, age_range, max_age, min_age,
|
||||
ge_amount, amount_range, lt_amount, epoch, class, type_, ..
|
||||
sth,
|
||||
lth,
|
||||
age_range,
|
||||
max_age,
|
||||
min_age,
|
||||
ge_amount,
|
||||
amount_range,
|
||||
lt_amount,
|
||||
epoch,
|
||||
class,
|
||||
type_,
|
||||
..
|
||||
} = self;
|
||||
|
||||
// All remaining groups run in parallel. Each closure owns an exclusive &mut
|
||||
@@ -465,17 +488,108 @@ impl UTXOCohorts<Rw> {
|
||||
let ss = &all_supply_sats;
|
||||
|
||||
let tasks: Vec<Box<dyn FnOnce() -> Result<()> + Send + '_>> = vec![
|
||||
Box::new(|| sth.metrics.compute_rest_part2(blocks, prices, starting_indexes, height_to_market_cap, vc, vd, ss, exit)),
|
||||
Box::new(|| lth.metrics.compute_rest_part2(blocks, prices, starting_indexes, height_to_market_cap, ss, exit)),
|
||||
Box::new(|| age_range.par_iter_mut().try_for_each(|v| v.metrics.compute_rest_part2(blocks, prices, starting_indexes, height_to_market_cap, ss, exit))),
|
||||
Box::new(|| max_age.par_iter_mut().try_for_each(|v| v.metrics.compute_rest_part2(blocks, prices, starting_indexes, height_to_market_cap, ss, exit))),
|
||||
Box::new(|| min_age.par_iter_mut().try_for_each(|v| v.metrics.compute_rest_part2(blocks, prices, starting_indexes, height_to_market_cap, ss, exit))),
|
||||
Box::new(|| ge_amount.par_iter_mut().try_for_each(|v| v.metrics.compute_rest_part2(prices, starting_indexes, exit))),
|
||||
Box::new(|| epoch.par_iter_mut().try_for_each(|v| v.metrics.compute_rest_part2(blocks, prices, starting_indexes, height_to_market_cap, ss, exit))),
|
||||
Box::new(|| class.par_iter_mut().try_for_each(|v| v.metrics.compute_rest_part2(blocks, prices, starting_indexes, height_to_market_cap, ss, exit))),
|
||||
Box::new(|| amount_range.par_iter_mut().try_for_each(|v| v.metrics.compute_rest_part2(prices, starting_indexes, exit))),
|
||||
Box::new(|| lt_amount.par_iter_mut().try_for_each(|v| v.metrics.compute_rest_part2(prices, starting_indexes, exit))),
|
||||
Box::new(|| type_.par_iter_mut().try_for_each(|v| v.metrics.compute_rest_part2(prices, starting_indexes, exit))),
|
||||
Box::new(|| {
|
||||
sth.metrics.compute_rest_part2(
|
||||
blocks,
|
||||
prices,
|
||||
starting_indexes,
|
||||
height_to_market_cap,
|
||||
vc,
|
||||
vd,
|
||||
ss,
|
||||
exit,
|
||||
)
|
||||
}),
|
||||
Box::new(|| {
|
||||
lth.metrics.compute_rest_part2(
|
||||
blocks,
|
||||
prices,
|
||||
starting_indexes,
|
||||
height_to_market_cap,
|
||||
ss,
|
||||
exit,
|
||||
)
|
||||
}),
|
||||
Box::new(|| {
|
||||
age_range.par_iter_mut().try_for_each(|v| {
|
||||
v.metrics.compute_rest_part2(
|
||||
blocks,
|
||||
prices,
|
||||
starting_indexes,
|
||||
height_to_market_cap,
|
||||
ss,
|
||||
exit,
|
||||
)
|
||||
})
|
||||
}),
|
||||
Box::new(|| {
|
||||
max_age.par_iter_mut().try_for_each(|v| {
|
||||
v.metrics.compute_rest_part2(
|
||||
blocks,
|
||||
prices,
|
||||
starting_indexes,
|
||||
height_to_market_cap,
|
||||
ss,
|
||||
exit,
|
||||
)
|
||||
})
|
||||
}),
|
||||
Box::new(|| {
|
||||
min_age.par_iter_mut().try_for_each(|v| {
|
||||
v.metrics.compute_rest_part2(
|
||||
blocks,
|
||||
prices,
|
||||
starting_indexes,
|
||||
height_to_market_cap,
|
||||
ss,
|
||||
exit,
|
||||
)
|
||||
})
|
||||
}),
|
||||
Box::new(|| {
|
||||
ge_amount
|
||||
.par_iter_mut()
|
||||
.try_for_each(|v| v.metrics.compute_rest_part2(prices, starting_indexes, exit))
|
||||
}),
|
||||
Box::new(|| {
|
||||
epoch.par_iter_mut().try_for_each(|v| {
|
||||
v.metrics.compute_rest_part2(
|
||||
blocks,
|
||||
prices,
|
||||
starting_indexes,
|
||||
height_to_market_cap,
|
||||
ss,
|
||||
exit,
|
||||
)
|
||||
})
|
||||
}),
|
||||
Box::new(|| {
|
||||
class.par_iter_mut().try_for_each(|v| {
|
||||
v.metrics.compute_rest_part2(
|
||||
blocks,
|
||||
prices,
|
||||
starting_indexes,
|
||||
height_to_market_cap,
|
||||
ss,
|
||||
exit,
|
||||
)
|
||||
})
|
||||
}),
|
||||
Box::new(|| {
|
||||
amount_range
|
||||
.par_iter_mut()
|
||||
.try_for_each(|v| v.metrics.compute_rest_part2(prices, starting_indexes, exit))
|
||||
}),
|
||||
Box::new(|| {
|
||||
lt_amount
|
||||
.par_iter_mut()
|
||||
.try_for_each(|v| v.metrics.compute_rest_part2(prices, starting_indexes, exit))
|
||||
}),
|
||||
Box::new(|| {
|
||||
type_
|
||||
.par_iter_mut()
|
||||
.try_for_each(|v| v.metrics.compute_rest_part2(prices, starting_indexes, exit))
|
||||
}),
|
||||
];
|
||||
|
||||
tasks
|
||||
@@ -589,7 +703,7 @@ impl UTXOCohorts<Rw> {
|
||||
/// Filter source cohorts by an optional filter.
|
||||
/// If filter is None, returns all sources (used for "all" aggregate).
|
||||
fn filter_sources_from<'a, M: CohortMetricsBase + 'a>(
|
||||
sources: impl Iterator<Item = &'a UTXOCohortVecs<M, RealizedState>>,
|
||||
sources: impl Iterator<Item = &'a UTXOCohortVecs<M>>,
|
||||
filter: Option<&Filter>,
|
||||
) -> Vec<&'a M> {
|
||||
match filter {
|
||||
@@ -603,7 +717,7 @@ fn filter_sources_from<'a, M: CohortMetricsBase + 'a>(
|
||||
|
||||
/// Filter MinimalCohortMetrics source cohorts by an optional filter.
|
||||
fn filter_minimal_sources_from<'a>(
|
||||
sources: impl Iterator<Item = &'a UTXOCohortVecs<MinimalCohortMetrics, MinimalRealizedState>>,
|
||||
sources: impl Iterator<Item = &'a UTXOCohortVecs<MinimalCohortMetrics>>,
|
||||
filter: Option<&Filter>,
|
||||
) -> Vec<&'a MinimalCohortMetrics> {
|
||||
match filter {
|
||||
|
||||
@@ -7,28 +7,27 @@ use vecdb::{Exit, ReadableVec};
|
||||
use crate::{blocks, distribution::state::UTXOCohortState, prices};
|
||||
|
||||
use crate::distribution::metrics::{
|
||||
CohortMetricsBase, CoreCohortMetrics, MinimalCohortMetrics,
|
||||
CohortMetricsBase, CohortMetricsState, CoreCohortMetrics, MinimalCohortMetrics,
|
||||
};
|
||||
use crate::distribution::state::{CoreRealizedState, MinimalRealizedState, RealizedOps, RealizedState};
|
||||
|
||||
use super::super::traits::DynCohortVecs;
|
||||
|
||||
#[derive(Traversable)]
|
||||
pub struct UTXOCohortVecs<Metrics, R: RealizedOps> {
|
||||
pub struct UTXOCohortVecs<M: CohortMetricsState> {
|
||||
#[traversable(skip)]
|
||||
state_starting_height: Option<Height>,
|
||||
|
||||
#[traversable(skip)]
|
||||
pub state: Option<Box<UTXOCohortState<R>>>,
|
||||
pub state: Option<Box<UTXOCohortState<M::Realized>>>,
|
||||
|
||||
#[traversable(flatten)]
|
||||
pub metrics: Metrics,
|
||||
pub metrics: M,
|
||||
}
|
||||
|
||||
// --- Shared state helpers (identical across all DynCohortVecs impls) ---
|
||||
|
||||
impl<Metrics, R: RealizedOps> UTXOCohortVecs<Metrics, R> {
|
||||
pub(crate) fn new(state: Option<Box<UTXOCohortState<R>>>, metrics: Metrics) -> Self {
|
||||
impl<M: CohortMetricsState> UTXOCohortVecs<M> {
|
||||
pub(crate) fn new(state: Option<Box<UTXOCohortState<M::Realized>>>, metrics: M) -> Self {
|
||||
Self {
|
||||
state_starting_height: None,
|
||||
state,
|
||||
@@ -66,17 +65,13 @@ impl<Metrics, R: RealizedOps> UTXOCohortVecs<Metrics, R> {
|
||||
|
||||
// --- Blanket impl for CohortMetricsBase types (always use full RealizedState) ---
|
||||
|
||||
impl<Metrics: CohortMetricsBase + Traversable> Filtered
|
||||
for UTXOCohortVecs<Metrics, RealizedState>
|
||||
{
|
||||
impl<M: CohortMetricsBase + Traversable> Filtered for UTXOCohortVecs<M> {
|
||||
fn filter(&self) -> &Filter {
|
||||
self.metrics.filter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Metrics: CohortMetricsBase + Traversable> DynCohortVecs
|
||||
for UTXOCohortVecs<Metrics, RealizedState>
|
||||
{
|
||||
impl<M: CohortMetricsBase + Traversable> DynCohortVecs for UTXOCohortVecs<M> {
|
||||
fn min_stateful_height_len(&self) -> usize {
|
||||
self.metrics.min_stateful_height_len()
|
||||
}
|
||||
@@ -227,13 +222,13 @@ macro_rules! impl_import_state {
|
||||
|
||||
// --- MinimalCohortMetrics: uses MinimalRealizedState ---
|
||||
|
||||
impl Filtered for UTXOCohortVecs<MinimalCohortMetrics, MinimalRealizedState> {
|
||||
impl Filtered for UTXOCohortVecs<MinimalCohortMetrics> {
|
||||
fn filter(&self) -> &Filter {
|
||||
&self.metrics.filter
|
||||
}
|
||||
}
|
||||
|
||||
impl DynCohortVecs for UTXOCohortVecs<MinimalCohortMetrics, MinimalRealizedState> {
|
||||
impl DynCohortVecs for UTXOCohortVecs<MinimalCohortMetrics> {
|
||||
fn min_stateful_height_len(&self) -> usize {
|
||||
self.metrics.min_stateful_height_len()
|
||||
}
|
||||
@@ -311,13 +306,13 @@ impl DynCohortVecs for UTXOCohortVecs<MinimalCohortMetrics, MinimalRealizedState
|
||||
|
||||
// --- CoreCohortMetrics: uses CoreRealizedState ---
|
||||
|
||||
impl Filtered for UTXOCohortVecs<CoreCohortMetrics, CoreRealizedState> {
|
||||
impl Filtered for UTXOCohortVecs<CoreCohortMetrics> {
|
||||
fn filter(&self) -> &Filter {
|
||||
&self.metrics.filter
|
||||
}
|
||||
}
|
||||
|
||||
impl DynCohortVecs for UTXOCohortVecs<CoreCohortMetrics, CoreRealizedState> {
|
||||
impl DynCohortVecs for UTXOCohortVecs<CoreCohortMetrics> {
|
||||
fn min_stateful_height_len(&self) -> usize {
|
||||
self.metrics.min_stateful_height_len()
|
||||
}
|
||||
@@ -392,4 +387,3 @@ impl DynCohortVecs for UTXOCohortVecs<CoreCohortMetrics, CoreRealizedState> {
|
||||
self.reset_iteration_impl();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,8 +20,12 @@ pub(crate) fn compute_overlapping(
|
||||
) -> Result<()> {
|
||||
info!("Computing overlapping cohorts...");
|
||||
|
||||
utxo_cohorts.compute_overlapping_vecs(starting_indexes, exit)?;
|
||||
address_cohorts.compute_overlapping_vecs(starting_indexes, exit)?;
|
||||
let (r1, r2) = rayon::join(
|
||||
|| utxo_cohorts.compute_overlapping_vecs(starting_indexes, exit),
|
||||
|| address_cohorts.compute_overlapping_vecs(starting_indexes, exit),
|
||||
);
|
||||
r1?;
|
||||
r2?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -39,8 +43,12 @@ pub(crate) fn compute_rest_part1(
|
||||
) -> Result<()> {
|
||||
info!("Computing rest part 1...");
|
||||
|
||||
utxo_cohorts.compute_rest_part1(blocks, prices, starting_indexes, exit)?;
|
||||
address_cohorts.compute_rest_part1(blocks, prices, starting_indexes, exit)?;
|
||||
let (r1, r2) = rayon::join(
|
||||
|| utxo_cohorts.compute_rest_part1(blocks, prices, starting_indexes, exit),
|
||||
|| address_cohorts.compute_rest_part1(blocks, prices, starting_indexes, exit),
|
||||
);
|
||||
r1?;
|
||||
r2?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -164,11 +164,34 @@ pub use unrealized::*;
|
||||
use brk_cohort::Filter;
|
||||
use brk_error::Result;
|
||||
use brk_types::{Cents, Height, Indexes, Version};
|
||||
use vecdb::{AnyStoredVec, Exit};
|
||||
use vecdb::{AnyStoredVec, Exit, StorageMode};
|
||||
|
||||
use crate::{blocks, distribution::state::{CohortState, RealizedState}, prices};
|
||||
use crate::{blocks, distribution::state::{CohortState, CoreRealizedState, MinimalRealizedState, RealizedOps, RealizedState}, prices};
|
||||
|
||||
pub trait CohortMetricsBase: Send + Sync {
|
||||
pub trait CohortMetricsState {
|
||||
type Realized: RealizedOps;
|
||||
}
|
||||
|
||||
impl<M: StorageMode> CohortMetricsState for MinimalCohortMetrics<M> {
|
||||
type Realized = MinimalRealizedState;
|
||||
}
|
||||
impl<M: StorageMode> CohortMetricsState for CoreCohortMetrics<M> {
|
||||
type Realized = CoreRealizedState;
|
||||
}
|
||||
impl<M: StorageMode> CohortMetricsState for BasicCohortMetrics<M> {
|
||||
type Realized = RealizedState;
|
||||
}
|
||||
impl<M: StorageMode> CohortMetricsState for ExtendedCohortMetrics<M> {
|
||||
type Realized = RealizedState;
|
||||
}
|
||||
impl<M: StorageMode> CohortMetricsState for ExtendedAdjustedCohortMetrics<M> {
|
||||
type Realized = RealizedState;
|
||||
}
|
||||
impl<M: StorageMode> CohortMetricsState for AllCohortMetrics<M> {
|
||||
type Realized = RealizedState;
|
||||
}
|
||||
|
||||
pub trait CohortMetricsBase: CohortMetricsState<Realized = RealizedState> + Send + Sync {
|
||||
fn filter(&self) -> &Filter;
|
||||
fn supply(&self) -> &SupplyMetrics;
|
||||
fn supply_mut(&mut self) -> &mut SupplyMetrics;
|
||||
|
||||
@@ -600,6 +600,11 @@ fn type_contains_ident(ty: &Type, ident: &syn::Ident) -> bool {
|
||||
/// - Types with `M: StorageMode` → maps `Self<Rw>` → `Self<Ro>`.
|
||||
/// - Types with other generic type params (no M) → propagates `ReadOnlyClone` through each param.
|
||||
/// - Types with no generic type params → nothing generated (they should `#[derive(Clone)]`).
|
||||
///
|
||||
/// Container params (mapped through ReadOnlyClone) are identified by:
|
||||
/// - Unbounded type params (no inline or where-clause bounds), OR
|
||||
/// - Bounded params that appear as a bare field type in a non-skipped field
|
||||
/// (e.g. `metrics: M` where M is the param itself).
|
||||
fn gen_read_only_clone(input: &DeriveInput) -> proc_macro2::TokenStream {
|
||||
let generics = &input.generics;
|
||||
let name = &input.ident;
|
||||
@@ -627,8 +632,6 @@ fn gen_read_only_clone(input: &DeriveInput) -> proc_macro2::TokenStream {
|
||||
}
|
||||
|
||||
// Determine which type params have bounds (inline or via where clause).
|
||||
// Bounded params are "leaf" params — kept as-is in the ReadOnly target.
|
||||
// Unbounded params are "container" params — mapped through ReadOnlyClone.
|
||||
let where_bounded: Vec<&syn::Ident> = if let Some(where_clause) = &generics.where_clause {
|
||||
where_clause
|
||||
.predicates
|
||||
@@ -651,9 +654,17 @@ fn gen_read_only_clone(input: &DeriveInput) -> proc_macro2::TokenStream {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
// Find params that appear as bare (direct) field types in non-skipped fields.
|
||||
let bare_field_params = find_bare_field_params(data, &type_params);
|
||||
|
||||
// Container params: unbounded OR bare-field params.
|
||||
let container_params: Vec<&syn::Ident> = type_params
|
||||
.iter()
|
||||
.filter(|tp| tp.bounds.is_empty() && !where_bounded.contains(&&tp.ident))
|
||||
.filter(|tp| {
|
||||
let is_unbounded = tp.bounds.is_empty() && !where_bounded.contains(&&tp.ident);
|
||||
let is_bare = bare_field_params.contains(&&tp.ident);
|
||||
is_unbounded || is_bare
|
||||
})
|
||||
.map(|tp| &tp.ident)
|
||||
.collect();
|
||||
|
||||
@@ -662,7 +673,37 @@ fn gen_read_only_clone(input: &DeriveInput) -> proc_macro2::TokenStream {
|
||||
return quote! {};
|
||||
}
|
||||
|
||||
gen_read_only_clone_for_generics(name, generics, data, &container_params)
|
||||
gen_read_only_clone_for_generics(name, generics, data, &type_params, &container_params)
|
||||
}
|
||||
|
||||
/// Find type params that appear as bare (direct) field types in non-skipped fields.
|
||||
/// E.g. `metrics: M` where M is a type param → M is a bare field param.
|
||||
fn find_bare_field_params<'a>(
|
||||
data: &syn::DataStruct,
|
||||
type_params: &[&'a syn::TypeParam],
|
||||
) -> Vec<&'a syn::Ident> {
|
||||
let fields: &syn::punctuated::Punctuated<syn::Field, _> = match &data.fields {
|
||||
Fields::Named(named) => &named.named,
|
||||
Fields::Unnamed(unnamed) => &unnamed.unnamed,
|
||||
Fields::Unit => return Vec::new(),
|
||||
};
|
||||
|
||||
let mut bare = Vec::new();
|
||||
for field in fields {
|
||||
if is_field_skipped(field) {
|
||||
continue;
|
||||
}
|
||||
if let Type::Path(type_path) = &field.ty
|
||||
&& type_path.path.segments.len() == 1
|
||||
&& let Some(seg) = type_path.path.segments.first()
|
||||
&& seg.arguments.is_empty()
|
||||
{
|
||||
if let Some(tp) = type_params.iter().find(|tp| tp.ident == seg.ident) {
|
||||
bare.push(&tp.ident);
|
||||
}
|
||||
}
|
||||
}
|
||||
bare
|
||||
}
|
||||
|
||||
/// Generate `ReadOnlyClone` for types with `M: StorageMode`.
|
||||
@@ -798,14 +839,18 @@ fn is_field_skipped(field: &syn::Field) -> bool {
|
||||
|
||||
/// Generate `ReadOnlyClone` for types with generic type params but no `M: StorageMode`.
|
||||
///
|
||||
/// `container_params` are unbounded type params that get `ReadOnlyClone` bounds and are
|
||||
/// `container_params` are type params that get `ReadOnlyClone` bounds and are
|
||||
/// mapped to `T::ReadOnly` in the target type.
|
||||
/// Bounded type params (leaf params) are kept as-is — they don't change across storage modes.
|
||||
/// Leaf type params are kept as-is — they don't change across storage modes.
|
||||
/// Fields containing container params use `.read_only_clone()`, others use `.clone()`.
|
||||
///
|
||||
/// For bounded container params, the original bounds are preserved and propagated
|
||||
/// to the ReadOnly version via where clause (e.g. `M::ReadOnly: CohortMetricsState`).
|
||||
fn gen_read_only_clone_for_generics(
|
||||
name: &syn::Ident,
|
||||
generics: &syn::Generics,
|
||||
data: &syn::DataStruct,
|
||||
type_params: &[&syn::TypeParam],
|
||||
container_params: &[&syn::Ident],
|
||||
) -> proc_macro2::TokenStream {
|
||||
// Check if any non-skipped field references a container param (otherwise skip).
|
||||
@@ -832,6 +877,7 @@ fn gen_read_only_clone_for_generics(
|
||||
let is_container = |ident: &syn::Ident| container_params.iter().any(|cp| *cp == ident);
|
||||
|
||||
// Impl generics: add ReadOnlyClone bound to container params, keep bounds for leaf params.
|
||||
// For bounded container params, preserve original bounds alongside ReadOnlyClone.
|
||||
let impl_params: Vec<proc_macro2::TokenStream> = generics
|
||||
.params
|
||||
.iter()
|
||||
@@ -840,7 +886,11 @@ fn gen_read_only_clone_for_generics(
|
||||
let ident = &tp.ident;
|
||||
let bounds = &tp.bounds;
|
||||
if is_container(ident) {
|
||||
quote! { #ident: vecdb::ReadOnlyClone }
|
||||
if bounds.is_empty() {
|
||||
quote! { #ident: vecdb::ReadOnlyClone }
|
||||
} else {
|
||||
quote! { #ident: #bounds + vecdb::ReadOnlyClone }
|
||||
}
|
||||
} else if bounds.is_empty() {
|
||||
quote! { #ident }
|
||||
} else {
|
||||
@@ -900,7 +950,48 @@ fn gen_read_only_clone_for_generics(
|
||||
})
|
||||
.collect();
|
||||
|
||||
let where_clause = &generics.where_clause;
|
||||
// Build where clause: propagate bounds from bounded container params to their ReadOnly.
|
||||
// E.g. `M: Trait` → add `<M as ReadOnlyClone>::ReadOnly: Trait`.
|
||||
let mut extra_where: Vec<proc_macro2::TokenStream> = Vec::new();
|
||||
|
||||
// Propagate inline bounds.
|
||||
for tp in type_params {
|
||||
if is_container(&tp.ident) && !tp.bounds.is_empty() {
|
||||
let ident = &tp.ident;
|
||||
let bounds = &tp.bounds;
|
||||
extra_where.push(quote! {
|
||||
<#ident as vecdb::ReadOnlyClone>::ReadOnly: #bounds
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Propagate where-clause bounds for container params.
|
||||
if let Some(wc) = &generics.where_clause {
|
||||
for pred in &wc.predicates {
|
||||
if let syn::WherePredicate::Type(pt) = pred
|
||||
&& let Type::Path(tp) = &pt.bounded_ty
|
||||
&& let Some(seg) = tp.path.segments.first()
|
||||
&& container_params.iter().any(|cp| **cp == seg.ident)
|
||||
{
|
||||
let ident = &seg.ident;
|
||||
let bounds = &pt.bounds;
|
||||
extra_where.push(quote! {
|
||||
<#ident as vecdb::ReadOnlyClone>::ReadOnly: #bounds
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let original_predicates = generics
|
||||
.where_clause
|
||||
.as_ref()
|
||||
.map(|w| &w.predicates);
|
||||
|
||||
let combined_where = if extra_where.is_empty() && original_predicates.is_none() {
|
||||
quote! {}
|
||||
} else {
|
||||
quote! { where #(#extra_where,)* #original_predicates }
|
||||
};
|
||||
|
||||
// Field-level: if field type contains any container param → read_only_clone, else → clone.
|
||||
let field_contains_container_param =
|
||||
@@ -954,7 +1045,7 @@ fn gen_read_only_clone_for_generics(
|
||||
};
|
||||
|
||||
quote! {
|
||||
impl<#(#impl_params),*> vecdb::ReadOnlyClone for #name<#(#self_ty_args),*> #where_clause {
|
||||
impl<#(#impl_params),*> vecdb::ReadOnlyClone for #name<#(#self_ty_args),*> #combined_where {
|
||||
type ReadOnly = #name<#(#ro_ty_args),*>;
|
||||
|
||||
fn read_only_clone(&self) -> Self::ReadOnly {
|
||||
|
||||
Reference in New Issue
Block a user