Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Activity
    Daniel Neveux
    @dagatsoin
    I prefer pastas...
    Jean-Jacques Dubray
    @jdubray
    no offense, but the purpose of nap() is to deal with "automatic actions" that need to be triggered once you reach a particular state.
    pasta lack the "ball" structure
    The big question is really how do you deal with effects?
    And I would argue that you can actually run a query in an action and propose the results of the query to the model to mutate the application state.
    I do agree though that models are a bit weak in SAM, but the way you should probably implement them (specially when they are heavy like requiring a query) would be to have a local SAM instance that deals with the modal, from there you would trigger an action back to the parent SAM instance (something like submit, or with nap() if the action is automatic and requires the model to disappear).
    SAM instances are very light weight. So there is no real overhead in adding a SAM instance just for a modal.
    Jean-Jacques Dubray
    @jdubray
    You have two cases:
    • the panel is built after the query returned its results
    • the panel appears, the query then runs and update the state of the panel
    In the first case you don't need a separate instance and I would put the query (an effect) in the action, the action would present the results of the query, the model would update with:
    showPanel = true
    paneData = data
    and the State function would render the panel accordingly
    that way you have "ball" effect
    In case two, yes, you have to use nap() to trigger the query, but again, there is no need write some yarn code to keep track of the query state. I would only add some code for cleaning up the panel in case of timeout (the action never present the data).
    Would that work for you?
    In Multi Player games, SAM is particularly effective because all players can share the same application state and you never lose track of which action, from any player are possible, at a given point in time. The model acts as a critical section that processes only one action at a time.
    Jean-Jacques Dubray
    @jdubray
    Similarly, if there are any updates to the application state that need to be persisted, I would do it right there in the model rather than in nap(). Theoretically nap() is where it should happen, but IMHO, it creates too many opportunity for race conditions, i.e. the model is free to process a new action before the persistence can succeed or fail. IMHO, it's not worth it.
    Are you aware of the SAM-SAFE middleware? https://www.npmjs.com/package/sam-safe
    Daniel Neveux
    @dagatsoin
    Very interesting, indeed I am using two sam instances, one for server, one for the client (in game you need a local simulation of the world to avoid lag)
    My exemple was exagerate to make the point of the spagethi code if I use nap for everything.
    Jean-Jacques Dubray
    @jdubray
    Yes, again, in theory you should, but in practice I believe it better not too, you should use nap() only for something that needs to happen once you reach a particular state that no user would be able to trigger for instance. In any case, I would not use it at all for queries and updates.
    Let me know if you keep using SAM, I'd be more than happy to add you to the list!!
    Daniel Neveux
    @dagatsoin
    I will, indeed I have already implemented it from scratch with some modification to fits my need.
    The main change I made is for semantic. For exemple State is Status. And I keep State to represent a Model State. Which is simply a plain object that I wire to the view (react in my case but work for anything else) with MobxJS
    Daniel Neveux
    @dagatsoin
    Also, as I am in a game, all the action context logic (exemple: a player who heal another player) is a set of rule that the GameMasterService use to determine if the action is allowed or not. So some actions could be aborted without modified the model.
    Jean-Jacques Dubray
    @jdubray
    I would not underestimate the power of the State() function (including nap() that you are using)
    State() enables a complete decoupling of the view components from the application.
    Daniel Neveux
    @dagatsoin
    I keep using State() but it is a Singleton that I called App() because I am more confortable to use the word State for something like a bunch of variable representing the state of an object at a time.
    Jean-Jacques Dubray
    @jdubray
    The view components should know nothing about your application.
    It's ok, you can use any name that suits you.
    Please note that SAFE enables what I call "action hang back"
    Daniel Neveux
    @dagatsoin
    (looking at it)
    Jean-Jacques Dubray
    @jdubray
    Hang back is a bit more generic than cancel, it allows you to trigger as many action you want and let the first one win.
    of course you can cancel a long running action (like a query) by presenting a cancellation to the model
    Daniel Neveux
    @dagatsoin
    mmmh interesting, I have not implemented a fine way to cancel the action yet (appart from return; )
    Jean-Jacques Dubray
    @jdubray
    SAFE makes it generic, it keeps track of "steps" and assign unique ids to all action instances, so that for any given "step" only action can present data to the model
    It should be very helpful in your scenario
    Daniel Neveux
    @dagatsoin
    yes
    Jean-Jacques Dubray
    @jdubray
    can even make it fun for players to interact with each other without writing too much code
    Daniel Neveux
    @dagatsoin
    when ninjas grind a chest to be the first to get the treasureƧ
    Jean-Jacques Dubray
    @jdubray
    yes, excactly
    Daniel Neveux
    @dagatsoin
    :)
    I have also encapsulated the action to have a dev tool which keep traces of the triggered actions. This way the main loop is complety pure. "If action A is triggered with model state B then next model state will be R"
    Jean-Jacques Dubray
    @jdubray
    Yes, SAFE also implements "time travel", I agree that's generally a good thing.
    In your case, that is essential for debugging/testing
    Daniel Neveux
    @dagatsoin
    Yes, and SAM has implemented a very strong mental model. The decoupling is very clear.
    Great job!
    Jean-Jacques Dubray
    @jdubray
    thank you! but it's really TLA+ and React, I just put two great pieces together, nothing more.
    By the way, Wizar looks like an amazing idea!! I can imagine the potential!
    Jean-Jacques Dubray
    @jdubray
    @dagatsoin One more thing about your question on Action composition. So logically, actions cannot be composed, the flow is propose/accept/learn, you cannot trigger another action before the application state has been mutated. That being said, you can create "composite" actions, this is possible/easier to do because of the dataset signature of the present method. You can create composite actions through functional composition A(B(data)) or even C(A(data), B(data))... but you are not triggering multiple "actions" per se. You merely decompose and recompose a single action.
    Daniel Neveux
    @dagatsoin

    Thx for Wizar, I do my best :)
    I understand the need to not trigger another action when model is still mutating. But I don't see how to compose action. Or maybe I am a bit lost in translation around your explanation. Have you got a simple concrete exemple of action composition ?
    For example, does it fit the requirements?

    const actionGiveMana = (manaPts, model) => {
      //somelogic
      ...
      model.present(data); 
    }
    
    const actionHeal = (healPts, model) => {
      //somelogic
      ...
      model.present(data);
    }
    
    const actionCare = (manaPts, healPts, model) => {
      actionGiveMana(healPts, model);
      actionHeal(healPts, model);
      //somelogic
      ...
      model.present(data3, model);
    }

    In the case of firing actionCarethe model will be mutated twice before the final effect. But in my mind, this is still a pure function because we can reproduce the same effect with the same initial model state.

    Jean-Jacques Dubray
    @jdubray
    An action represents semantically a proposal to update the application state in one "step", as a unit of work if you prefer. It is also a unit of authorization, given a particular state (battling, sleeping...) is an action authorized to be triggered. As such the "actionCare" is not valid, or at least you have to make sure that the actionGiveMana "step" is complete before you trigger actionHeal.
    It's ok to pass the model as an input to the action, with the understanding that no mutation should occur there. The mutation occurs when the action invokes model.present.

    I would prefer see something like this if it makes sense to you:

    var careData = actionHeal_(actionGiveMana_(healPts,model),model)
    mode.present(careData)

    where actionHeal() and actionGiveMana() are the pure function generating the input to the present method

    It would look like:
    const actionHeal = (healPts, model) => {
      model.present(actionHeal_(healPts, model));
    }