1- # Brownfield Store
1+ # Brownie Architecture
22
3- Shared state management between React Native and Native apps (iOS/Android ).
3+ Shared state management between React Native and Native apps (iOS).
44
5- ## Architecture
5+ ## High-Level Architecture
66
77```
88┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
@@ -23,18 +23,14 @@ Shared state management between React Native and Native apps (iOS/Android).
2323 │ ▼
2424┌───────────────────┐ ┌───────────────────────┐
2525│ JS/React Hook │ │ Native Store │
26- │ useBrownfield() │ │ (Swift/Kotlin) │
26+ │ useBrownfield() │ │ (Swift) │
2727└───────────────────┘ └───────────────────────┘
2828```
2929
3030## CLI
3131
3232The ` brownie ` CLI generates native store types from TypeScript schema.
3333
34- ### Installation
35-
36- CLI is included in ` @callstack/brownie ` package.
37-
3834### Usage
3935
4036``` bash
@@ -217,7 +213,7 @@ Output goes to `lib/scripts/` and is exposed via `bin` field in `package.json`.
217213│ │ BrownieHostObject (jsi::HostObject) │ │
218214│ │ - get(prop) -> converts folly::dynamic to jsi::Value │ │
219215│ │ - set(prop, val) -> converts jsi::Value to folly::dynamic│ │
220- │ │ - unbox () -> full snapshot as jsi::Object │ │
216+ │ │ - getPropertyNames () -> list of property names │ │
221217│ └───────────────────────────────────────────────────────────┘ │
222218└─────────────────────────────────────────────────────────────────┘
223219 │
@@ -269,8 +265,9 @@ packages/brownie/
269265
270266- ` get(prop) ` - Read property, converts ` folly::dynamic ` → ` jsi::Value `
271267- ` set(prop, value) ` - Write property, converts ` jsi::Value ` → ` folly::dynamic `
272- - ` unbox() ` - Returns full state snapshot
273- - Custom ` dynamicToJSI ` /` jsiToDynamic ` converters
268+ - ` unbox() ` - Returns full state snapshot as JSI object
269+ - ` getPropertyNames() ` - Returns list of all property names
270+ - Uses built-in ` jsi::valueFromDynamic ` /` jsi::dynamicFromValue ` converters
274271
275272### iOS Bridge
276273
@@ -291,6 +288,7 @@ packages/brownie/
291288- ` init(_ initialState, key:) ` - Create store, register with C++, push initial state
292289- ` set(_:) ` - Update state with closure
293290- ` set(_:to:) ` - Update via keypath
291+ - ` get(_:) ` - Get property via keypath
294292- Observes ` BrownieStoreUpdated ` notification, rebuilds typed state from C++ snapshot
295293- ` deinit ` removes notification observer
296294
@@ -302,9 +300,8 @@ packages/brownie/
302300
303301** @UseStore ** - SwiftUI property wrapper:
304302
305- - ` init(_ keyPath, key:) ` - Access specific property
306- - ` wrappedValue ` - Current value
307- - ` projectedValue ` - Access to full store for mutations
303+ - Uses ` BrownieStoreProtocol ` to automatically derive store key via ` storeName `
304+ - ` wrappedValue ` - Access to full ` Store<State> `
308305
309306## JS API
310307
@@ -346,127 +343,104 @@ Internal `Map<string, StoreCache>` holds:
346343- Current snapshot
347344- Registered listeners
348345
349- Snapshots refresh on ` BrownieStoreUpdated ` native event .
346+ Snapshots refresh on native store change callback .
350347
351- ## Auto-generation Hooks
348+ ## Data Transfer & Memory Model
352349
353- ### iOS (Podfile)
350+ ### Overview
354351
355- ` ` ` ruby
356- pre_install do | installer |
357- system (" npx" , " brownie" , " codegen" , " -p" , " swift" )
358- end
359- ` ` `
360-
361- ### Android (build.gradle.kts)
352+ Data flows through multiple layers with **full copies** at each boundary crossing. There is no shared memory or zero-copy optimization - each layer maintains its own representation.
362353
363- ` ` ` kotlin
364- tasks . register ( " generateBrownfieldStore " ) {
365- exec { commandLine( " npx " , " brownie " , " codegen " , " -p " , " kotlin " ) }
366- }
367- preBuild . dependsOn ( " generateBrownfieldStore " )
354+ ` ` `
355+ ┌─────────────┐ copy ┌─────────────┐ copy ┌─────────────┐
356+ │ JS Object │ ─────────▶ │ folly :: │ ─────────▶ │ NSDictionary │
357+ │ ( jsi ) │ ◀───────── │ dynamic │ ◀───────── │ / Swift │
358+ └─────────────┘ copy └─────────────┘ copy └─────────────┘
368359` ` `
369360
370- ## TODO
371-
372- ### Codegen
373-
374- - [x] Support multiple stores in package.json config (array of store configs)
375- - [x] Auto-discover stores from ` *.brownie .ts ` files (no manual config needed)
376- - [x] Add CLI tests
377- - [x] Generate store keys enum/constants for type safety between JS and Native
378-
379- ### Native Runtime
380-
381- - [x] Improve iOS runtime code (C++ core with folly::dynamic)
382- - [x] Extract C++ JSI layer to be shared across Android and iOS
383- - [ ] Android native runtime implementation (Store, StoreManager, JSI bridge)
384- - [ ] Keypath→string codegen for optimized single-key updates (future)
361+ Note: In the future we may explore optimizations like shared memory or zero-copy techniques, but for now simplicity and correctness are prioritized.
385362
386- ### JS Runtime
363+ ### C++ Layer (Source of Truth)
387364
388- - [x] useState-like hook API ( ` useBrownieStore ` returns ` [state , setState ]` )
389- - [x] setState supports callback pattern ` (prev ) => partial `
390- - [ ] Optimize re-renders on the JS side (selector support)
391- - [ ] Zustand integration
365+ **Writes use move semantics where possible:**
392366
393- ## Re-render Optimization Plan
367+ - ` BrownieStore :: set (key , value )` - value is moved into state: ` state_ [key ] = std :: move (value )`
368+ - ` BrownieStore :: setState (state )` - entire state is moved: ` state_ = std :: move (state )`
369+ - ` BrownieStoreManager :: registerStore (key , store )` - shared_ptr is moved into map
394370
395- ### Problem
371+ **Reads always copy:**
396372
397- Both JS and Swift currently re-render all subscribers when any property changes.
373+ - ` BrownieStore :: get (key )` - returns copy of ` folly :: dynamic ` value
374+ - ` BrownieStore :: getSnapshot ()` - returns copy of entire state
398375
399- **JS**: ` useBrownieStore ` returns full state → any change triggers all consumers
400- **Swift**: ` @Published state ` replaced wholesale → all ` @UseStore ` views re-render
376+ ### JSI Boundary (JS ↔ C++)
401377
402- ### JavaScript Solution: Selector Hook
378+ **JS → C++ (writes):**
403379
404- Add ` useBrownieStoreSelector ` that only re-renders when selected value changes:
380+ 1. ` jsi :: dynamicFromValue (rt , value )` creates new ` folly :: dynamic ` from JS value
381+ 2. Value is then moved into C++ store
405382
406- ` ` ` ts
407- function useBrownieStoreSelector <K extends keyof BrownieStores , T >(
408- key : K ,
409- selector : (state : BrownieStores [K ]) => T ,
410- isEqual ? : (a : T , b : T ) => boolean
411- ): T ;
412- ` ` `
383+ **C++ → JS (reads):**
413384
414- **Usage:**
385+ 1. ` folly :: dynamic ` is copied from store
386+ 2. ` jsi :: valueFromDynamic (rt , dynamic )` creates new JS value
415387
416- ` ` ` ts
417- const counter = useBrownieStoreSelector (' BrownfieldStore' , (s ) => s .counter );
418- const user = useBrownieStoreSelector (' BrownfieldStore' , (s ) => s .user );
419- ` ` `
388+ ### ObjC++ Bridge (C++ ↔ Swift)
420389
421- **Implementation :**
390+ **Swift → C++ (writes) :**
422391
423- 1. Store previous selected value in ` useRef `
424- 2. On store change, run selector on new snapshot
425- 3. Compare with previous using ` isEqual ` (default: ` Object . is ` )
426- 4. Only trigger re-render if different
392+ 1. Swift encodes ` Codable ` state to JSON via ` JSONEncoder `
393+ 2. JSON deserialized to ` NSDictionary `
394+ 3. ` convertIdToFollyDynamic ( dict ) ` creates ` folly :: dynamic ` copy
395+ 4. Value moved into C++ store
427396
428- ### Swift Solution: Selector Property Wrapper
397+ **C++ → Swift (reads):**
429398
430- Add ` @UseStoreSelector ` that subscribes to specific keypath:
399+ 1. ` folly :: dynamic ` copied from store via ` getSnapshot ()`
400+ 2. ` convertFollyDynamicToId (dynamic )` creates ` NSDictionary ` copy
401+ 3. ` JSONSerialization .data (withJSONObject : )` serializes to JSON
402+ 4. ` JSONDecoder ` deserializes to typed Swift struct (another copy)
431403
432- ` ` ` swift
433- @propertyWrapper
434- struct UseStoreSelector <State : BrownieStoreProtocol , Value : Equatable >: DynamicProperty {
435- let keyPath: KeyPath <State , Value >
436- @State private var value: Value
404+ ### Performance Implications
437405
438- init(_ keyPath : KeyPath <State , Value >)
439- var wrappedValue: Value { get }
440- }
441- ` ` `
406+ | Operation | Copies | Notes |
407+ | ------------------------ | ------ | --------------------------------- |
408+ | JS read single property | 2 | dynamic copy + JSI conversion |
409+ | JS read snapshot (unbox) | 2 | dynamic copy + JSI conversion |
410+ | JS write single property | 2 | JSI→dynamic + move into store |
411+ | Swift read snapshot | 4 | dynamic→NSDictionary→JSON→Codable |
412+ | Swift write full state | 4 | Codable→JSON→NSDictionary→dynamic |
442413
443- **Usage:**
414+ ### Thread Safety
444415
445- ` ` ` swift
446- @UseStoreSelector (\.counter ) var counter : Int
447- @UseStoreSelector (\.user ) var user : String
448- ` ` `
416+ - C++ store protected by ` std :: mutex ` on all operations
417+ - Swift ` StoreManager ` uses ` NSLock ` for registry access
418+ - Change notifications dispatched to main queue via ` dispatch_async `
449419
450- **Implementation:**
420+ ### Future Optimization Opportunities
451421
452- 1. Use ` @State ` instead of ` @StateObject ` (no ` ObservableObject ` dependency)
453- 2. Listen to ` BrownieStoreUpdated ` notification
454- 3. Extract value via keyPath from C++ snapshot
455- 4. Only update ` @State ` if value differs (Equatable check)
422+ The ObjC++ bridge layer has the most room for optimization:
456423
457- ### Tasks
424+ - **Skip JSON serialization** - Instead of ` folly :: dynamic → NSDictionary → JSON → Codable ` , expose individual property accessors from C++ directly to Swift. Eliminates 2 intermediate copies.
425+ - **Single-property sync** - Currently ` pushStateToCxx ()` serializes entire state. Could track dirty properties and only sync changed values.
426+ - **Lazy Swift state rebuild** - Defer ` Codable ` struct reconstruction until property actually accessed.
427+ - **Direct C++ ↔ Swift interop** - Swift 5.9+ has experimental C++ interop that could bypass ObjC++ bridge entirely.
458428
459- - [ ] JS: Implement ` useBrownieStoreSelector ` hook
460- - [ ] JS: Add ` isEqual ` option for custom comparison (objects/arrays)
461- - [ ] Swift: Implement ` @UseStoreSelector ` property wrapper
462- - [ ] Swift: Consider ` @Observable ` macro for iOS 17+ (automatic property tracking)
429+ ## Auto-generation Hooks
463430
464- ### Distribution
431+ ### iOS (Podfile)
465432
466- - [ ] Support xcframework packaging (iOS)
467- - [ ] Support AAR packaging (Android)
468- - [ ] Figure out autolinking of generated code
433+ ` ` ` ruby
434+ pre_install do | installer |
435+ system (" npx" , " brownie" , " codegen" , " -p" , " swift" )
436+ end
437+ ` ` `
469438
470- ### Documentation
439+ ### Android (build.gradle.kts)
471440
472- - [ ] Documentation
441+ ` ` ` kotlin
442+ tasks .register (" generateBrownfieldStore" ) {
443+ exec { commandLine(" npx" , " brownie" , " codegen" , " -p" , " kotlin" ) }
444+ }
445+ preBuild .dependsOn (" generateBrownfieldStore" )
446+ ` ` `
0 commit comments