Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to send another action as a consequence of an action? #9

Closed
pontifechs opened this issue Feb 15, 2015 · 7 comments
Closed

How to send another action as a consequence of an action? #9

pontifechs opened this issue Feb 15, 2015 · 7 comments

Comments

@pontifechs
Copy link

Is there any way to send another action as part of the handling of another action inside a store? And maybe this is more of a 'this isn't the flux way' question, but I'm having trouble trying to google for anybody trying to do this sort of thing.

So here's my setup. I'm essentially making a bejeweled clone, and I'm trying to get the cascading clears to work using flux actions. I have a swapTile action, but after every time the board changes as a result of this action, I need to check if any other clears should be happening. I tried sending another action from inside the first swapTile action, but it just hangs. From what I understand (granted this is limited), this is to be expected from the way the dispatcher works.

I had the thought of essentially having the store save some data which would essentially tell the view to trigger the second cascade event, but I'm not sure I really like the fact that the Store (where most of this logic should live) relies on the view to send the next event(s). Is this the only way to do this? Or is there a more flux-y way to do this sort of thing?

@goatslacker
Copy link
Owner

Wow this is a little tricky since you have to deal with animations and such. Personally, I'd keep the business logic out of the stores and keep the stores as just a representation of the state of the game. You can create a special component (a view controller?) which has most of the business logic, or put all that sort of logic in a util that gets called through the action. This is probably the flux way of doing things.

Anyway, the answer to your question. The dispatcher prevents you from firing actions while an action is still in the dispatch loop and this is because you don't want to get into a code mess where firing off a single action causes a set of cascading actions to take place. This is what flux was trying to avoid with MVC in the first place.

While it's not recommended I can let you know how to do it. One way around this limitation is to move the action dispatch out of the main loop via something like setTimeout or setImmediate. In alt, there's a helper function associated with every action which lets you do this sort of thing defer.

Here's an example on using defer:

// fire a butt load of actions
MyActions.prototype.swapTile = function (tile1, tile2) {
  this.dispatch([tile1, tile2])
  this.actions.saveUndoState.defer(tile1)
  this.actions.checkBoard.defer()
  this.actions.saveGame.defer()
  this.actions.checkGameOver.defer()
}

this.actions contains all your other actions defined in this fictional MyActions.prototype. You may call defer with however many arguments as you want. Defer is just like a setImmediate

Good luck!

@philippotto
Copy link

@goatslacker Correct me, if I'm wrong, but isn't something like the following perfectly fine?

MyActions.prototype.swapTile = function (tile1, tile2) {
  this.dispatch([tile1, tile2])
  this.actions.saveUndoState();
  this.actions.checkBoard();
}

Strictly speaking, no dispatch is initiated while another one is active. Instead, the first dispatch is initiated and completed and then the second and third one are processed.

I just tested it and alt doesn't seem to prevent this sort of "action delegation". Am I missing something?

@pontifechs
Copy link
Author

I haven't had a chance to work on this particular side-project in depth since I created this issue, but I tried this really briefly, and this does work as I'd expect with the defer. Without the defer, the actions and their corresponding handlers in the store are called, but the store only emits one change event to the view layer. Having multiple change events to the view layer was what i was really after with this.

The defer solves most of my problem, but I'm still having trouble finding a way to coordinate which actions should be done. For example, I'd like to do something like this:

MyActions.prototype.swapTile = function (tile1, tile2) {
  this.dispatch([tile1, tile2]);
  this.actions.checkBoard.defer();    // Clears a single 3-in-a-row, and somehow magically
                                      // writes out this clearedLastCheck condition
  while(clearedLastCheck)
  {
    this.actions.checkBoard.defer();
  }
}

Unfortunately I have no idea how checkBoard might tell this function what clearedLastCheck should be each iteration.

Luckily, with the way some of the game logic in my clone works out, I can always say that a cascade won't be longer than 13. It's not a great solution, but I can just call checkBoard 13 times.

All in all, not being able to do this sort of thing seems to be one of the explicit design goals behind flux. So I don't really have a problem with needing to abuse the framework to try and get it to do something it was designed not to do.

@philippotto
Copy link

Without the defer, the actions and their corresponding handlers in the store are called, but the store only emits one change event to the view layer.

That is weird, because when I tested it (and I just retested it), every dispatch call resulted in a change event in the view. If this was not the case, I would have thought that this was a bug in alt.

Maybe the reason for the problem is somewhere else? But I'm glad you found a workaround.

@goatslacker I would be happy to hear some clarification regarding the multiple dispatches and the view events, since I'm just starting to use alt and want to do it right.

@goatslacker
Copy link
Owner

@philippotto good catch, this wasn't working before.

Here's a proper broken example that needs the defer

var Alt = require('alt')

var alt = new Alt()

function MyActions() {
  this.generateActions('gameover', 'saveUndoState')
}

MyActions.prototype.checkBoard = function () {
  console.log('checking the board')

  if (true) {
    this.actions.gameover()
  }
}

MyActions.prototype.swapTile = function (tile1, tile2) {
  this.dispatch([tile1, tile2])

  // This is ok to do:
//  this.actions.saveUndoState(tile2, tile1)
//  this.actions.checkBoard()
}

var TileActions = alt.createActions(MyActions)

function TileStoreSpec() {
  this.bindActions(TileActions)
  this.board = [2, 1]
  this.undo = null
}
TileStoreSpec.prototype.swapTile = function (x) {
  this.board = x

  // Needs defer:
  TileActions.saveUndoState(x[1], x[0])
  TileActions.checkBoard()
}
TileStoreSpec.prototype.saveUndoState = function (x) {
  this.undo = x
}
TileStoreSpec.prototype.gameover = function () {
  this.board = null
}

var TileStore = alt.createStore(TileStoreSpec)

TileStore.listen(function () {
  console.log('Changed', TileStore.getState())
})

TileActions.swapTile(1, 2)

@philippotto
Copy link

@goatslacker Thank you, totally makes sense, now!

@goatslacker
Copy link
Owner

@pontifechs @philippotto thank you both for posting

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants