Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 23 additions & 25 deletions bench/dispatch-performance.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* gut check of "are we fast yet?"
*/

import History from '../src/history'
import Microcosm from '../src/microcosm'
import time from 'microtime'

Expand All @@ -15,6 +16,17 @@ const SAMPLES = 5

console.log('\nConducting dispatch benchmark...\n')

const action = n => n

const Domain = {
getInitialState: () => 0,
register() {
return {
[action]: (n) => n + 1
}
}
}

var results = SIZES.map(function (SIZE) {
/**
* Force garbage collection. This is exposed by invoking
Expand All @@ -23,20 +35,19 @@ var results = SIZES.map(function (SIZE) {
*/
global.gc()

var repo = new Microcosm({ maxHistory: Infinity })

var action = function test () {}
action.toString = function () { return 'test' }
/**
* Step 1. Build a history tree before making a Microcosm,
* this lets us isolate history building so we can specifically
* profile the dispatch process
*/
var history = new History({ limit: Infinity })

var Domain = {
getInitialState: () => 0,
register() {
return {
test: (n) => n + 1
}
}
for (var i = 0; i < SIZE; i++) {
history.append(action).close(true)
}

var repo = new Microcosm({ history })

/**
* Add the domain at multiple keys. This is a better simulation of actual
* applications. Otherwise, efficiencies are obtained enumerating over
Expand All @@ -48,18 +59,6 @@ var results = SIZES.map(function (SIZE) {
repo.addDomain('four', Domain)
repo.addDomain('five', Domain)

/**
* Append a given number of actions into history. We use this method
* instead of `::push()` for benchmark setup performance. At the time of writing,
* `push` takes anywhere from 0.5ms to 15ms depending on the sample range.
* This adds up to a very slow boot time!
*/
var startMemory = process.memoryUsage().heapUsed
for (var i = 0; i < SIZE; i++) {
repo.history.append(action).close(true)
}
var endMemory = process.memoryUsage().heapUsed

/**
* Warm up `::push()`
* This gives V8 time to analyze the types for the code.
Expand All @@ -78,8 +77,7 @@ var results = SIZES.map(function (SIZE) {

return {
'Actions' : SIZE.toLocaleString(),
'Rollforward' : average.toLocaleString() + 'ms',
'Memory Usage' : ((endMemory - startMemory) / 1000000).toFixed(2) + 'mbs'
'Rollforward' : average.toLocaleString() + 'ms'
}
})

Expand Down
17 changes: 8 additions & 9 deletions bench/history-performance.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ const results = SIZES.map(function (SIZE) {
*/
global.gc()

var action = () => {}
var stats = { build: 0, root: 0, merge: 0, size: 0, prune: 0, memory: 0 }
var history = new History()
var action = () => {}
var stats = { build: 0, root: 0, merge: 0, size: 0, prune: 0, memory: 0 }
var history = new History()

var memoryBefore = process.memoryUsage().heapUsed

Expand All @@ -45,7 +45,7 @@ const results = SIZES.map(function (SIZE) {
* the current branch.
*/
now = time.now()
history.size()
history.setSize()
stats.size = (time.now() - now) / 1000

/**
Expand All @@ -67,18 +67,16 @@ const results = SIZES.map(function (SIZE) {
}, {})
stats.merge = (time.now() - now) / 1000

var memoryUsage = process.memoryUsage().heapUsed - memoryBefore

/**
* Measure time to dispose all nodes in the history. This also has
* the side effect of helping to test memory leakage later.
*/
now = time.now()
history.prune(function() {
return true
})
history.toArray().reverse().forEach(a => a.close())
stats.prune = (time.now() - now) / 1000


/**
* Now that the history has been pruned, force garbage collection
* and record the increase in heap usage.
Expand All @@ -93,8 +91,9 @@ const results = SIZES.map(function (SIZE) {
'::append()': stats.build.toFixed(2) + 'ms',
'::toArray()': stats.toArray.toFixed(4) + 'ms',
'::reduce(merge)': stats.merge.toFixed(2) + 'ms',
'::size()': stats.size.toFixed(2) + 'ms',
'::setSize()': stats.size.toFixed(2) + 'ms',
'::prune()': stats.prune.toFixed(2) + 'ms',
'Memory': (memoryUsage / 1000000).toFixed(3) + 'mbs',
'Memory Growth': stats.memory.toFixed(3) + '%'
}
})
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
"immutable": "3.8.1",
"jsdom": "9.5.0",
"json-loader": "0.5.4",
"microcosm-debugger": "vigetlabs/microcosm-debugger",
"microcosm-debugger": "git+ssh://git@github.com/vigetlabs/microcosm-debugger.git",
"microtime": "2.1.1",
"nyc": "8.3.0",
"react": "~15.3.2",
Expand Down
18 changes: 10 additions & 8 deletions src/action.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,16 @@ export default class Action extends Emitter {
/**
* @param {Function} behavior - how the action should resolve.
*/
constructor (behavior) {
constructor (behavior, history) {
super()

this.depth = 0
this.behavior = tag(behavior)
this.type = null
this.payload = null
this.behavior = tag(behavior)
this.history = history
this.state = States.disabled
this.parent = null
this.sibling = null
this.state = States.disabled
this.type = null
}

/**
Expand Down Expand Up @@ -73,8 +73,8 @@ export default class Action extends Emitter {
* @private
* @return {Action} self
*/
execute (/** params **/) {
coroutine(this, this.behavior.apply(this, arguments))
execute (params) {
coroutine(this, this.behavior.apply(this, params))

return this
}
Expand All @@ -94,7 +94,9 @@ export default class Action extends Emitter {
this.payload = payload
}

this._emit('change', this.payload)
if (this.history) {
this.history.reconcile()
}

return this
}
Expand Down
32 changes: 12 additions & 20 deletions src/addons/presenter.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,10 @@ export default class Presenter extends Component {
this.pure = this._getRepoPurity(this.repo, this.props)

this._updatePropMap(this.props)

this.state = this._getState()

if (this.repo) {
this._listener = () => this._updateState()
this.repo.on('change', this._listener)
}
this.repo.on('change', this._updateState, this)
}

getChildContext () {
Expand Down Expand Up @@ -58,7 +56,7 @@ export default class Presenter extends Component {
}

/**
* Teardown subscriptions and other behavior.
* Opposite of setup. Useful for cleaning up side-effects.
*
* @param {Microcosm} repo - The presenter's Microcosm instance
* @param {Object} props - The presenter's props
Expand All @@ -67,10 +65,8 @@ export default class Presenter extends Component {
// NOOP
}

unsubscribe() {
if (this.repo) {
this.repo.off('change', this._listener)
}
shouldComponentUpdate (props, state) {
return !this.pure || !shallowEqual(state, this.state) || !shallowEqual(props, this.props)
}

componentWillMount() {
Expand All @@ -79,12 +75,13 @@ export default class Presenter extends Component {

componentWillUnmount () {
this.teardown(this.repo, this.props)
this.unsubscribe()
this.repo.teardown()
}

componentWillReceiveProps (nextProps) {
if (this.pure === false || shallowEqual(nextProps, this.props) === false) {
this._updatePropMap(nextProps)

this.update(this.repo, nextProps)
}

Expand Down Expand Up @@ -123,9 +120,9 @@ export default class Presenter extends Component {
* @private
*/
_updatePropMap (props) {
this.propMap = this.viewModel ? this.viewModel(props) : {}
this.propMap = this.viewModel(props)

if (this.repo && this.propMap === this.repo.state) {
if (this.propMap === this.repo.state) {
console.warn("The view model for this presenter returned repo.state. " +
"This method onlys run when a presenter is given new properties. " +
"If you would like to subscribe to all state changes, return a " +
Expand All @@ -148,7 +145,7 @@ export default class Presenter extends Component {
* @private
*/
_getState () {
const repoState = this._getRepoState()
const repoState = this.repo.state

if (typeof this.propMap === 'function') {
return this.propMap(repoState)
Expand All @@ -169,14 +166,9 @@ export default class Presenter extends Component {
* @private
*/
_getRepo() {
return this.props.repo || this.context.repo
}
const repo = this.props.repo || this.context.repo

/**
* @private
*/
_getRepoState() {
return this.repo ? this.repo.state : {}
return repo ? repo.fork() : new Microcosm()
}

/**
Expand Down
9 changes: 8 additions & 1 deletion src/domain.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ const EMPTY = {}

export default class Domain {

constructor() {
this.setup()
}

/**
* Setup runs right after a domain is added to a Microcosm, but before
* it rebases state to include the domain's `getInitialState` value. This
Expand All @@ -20,8 +24,11 @@ export default class Domain {
/**
* A default register function that just returns an empty object. This helps
* keep other code from branching.
*
* @param {string} type - The action type to respond to
* @return {object} a mapping of registrations
*/
register () {
register (type) {
// NOOP
return EMPTY
}
Expand Down
15 changes: 12 additions & 3 deletions src/domains/meta.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
/**
* Meta Domain
* A domain for managing lifecycle methods and other
* default behavior for other domains.
* A domain for managing lifecycle methods and other default behavior
* for other domains.
*/

import lifecycle from '../lifecycle'
import merge from '../merge'

export default {
[lifecycle.willReset]: (_, next) => next

[lifecycle.willReset](_old, next) {
return next
},

[lifecycle.willReplace](old, next) {
return merge({}, old, next)
}

}
Loading