{"id":13744643,"url":"https://github.com/Stray/GuardedCommandMap","last_synced_at":"2025-05-09T03:32:47.975Z","repository":{"id":136459261,"uuid":"1166428","full_name":"Stray/GuardedCommandMap","owner":"Stray","description":"robotlegs utility extension of CommandMap to provides guarding too","archived":false,"fork":false,"pushed_at":"2011-03-18T17:54:53.000Z","size":1154,"stargazers_count":14,"open_issues_count":1,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-11-15T16:41:09.665Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"ActionScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Stray.png","metadata":{"files":{"readme":"ReadMe.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2010-12-13T23:27:18.000Z","updated_at":"2019-02-27T22:20:19.000Z","dependencies_parsed_at":"2023-03-13T11:02:27.026Z","dependency_job_id":null,"html_url":"https://github.com/Stray/GuardedCommandMap","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Stray%2FGuardedCommandMap","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Stray%2FGuardedCommandMap/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Stray%2FGuardedCommandMap/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Stray%2FGuardedCommandMap/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Stray","download_url":"https://codeload.github.com/Stray/GuardedCommandMap/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253183236,"owners_count":21867388,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-03T05:01:13.213Z","updated_at":"2025-05-09T03:32:47.674Z","avatar_url":"https://github.com/Stray.png","language":"ActionScript","readme":"##What is a GuardedCommandMap?\n\nSometimes you want to map behaviour to an event, but only if certain other conditions are met.\n\nIt could be that the condition is relevant to some other property of the event - say the specific key being pressed in a KeyboardEvent - or it could be something else, for example whether the user already has local account details in a SOL.\n\nUsually we wind up implementing this kind of logic using if() statements and early bails in the execution of the Command.\n\nThe GuardedCommandMap abstracts the conditions from the actions.\n\n##Like how?\n\nAs well as mapping a Command, you also map one or more Guards. The Command is only executed if all the Guards agree to it.\n\nThis has the advantage of allowing you to map a Command as oneShot, but know that it won't be executed and unmapped unless all the Guards are passed.\n\n##What's a Guard?\n\nA Guard is very similar to a Command. It has only one public method: \n\t\n\tfunction approve():Boolean;\n\nThe Guard Classes are instantiated in the same way as Command classes - so they can have injections in the same way as the Command, and can receive the Event class that triggered the CommandMap, just as the Command eventually will.\n\nThe approve() method returns true or false. If all the approve() methods return true then the Command will be instantiated and will run. If any approve() method returns false then the process is aborted.      \n\n##Give me an example\n\nMy strategy game has a daily cycle which includes offering the player some casual labour, but we don't want to do this in the first three cycles of the game, while the player is still picking up the basics.\n\nSo - currently the command \"OfferExtraLabour\" has an execute like this:\n\n\toverride public function execute():void \n\t{\n\t\tif(calendarModel.daysPassed \u003e config.numberOfNormalDaysAtStart)\n\t\t{\n\t\t\tlabourSurpriseEventCaster.castSurpriseEvent();\n\t\t}\n\t\telse\n\t\t{\n\t\t\tlabourSurpriseEventCaster.castNormalEvent();\n\t\t}\n\t}\n\t\nBut that's a brittle condition. There no inherent link between the action and the condition. My logic is a little tangled.\n\nAnd, worse, there are a bunch of other things that I also don't want to kick in until day 3. So the logic is repeated in these other Commands as well as being tangled up with irrelevant things. If I decided to define the learning period differently I'd have to make changes in several places. We can do better!\n\nWith the GuardCommandMap I can refactor to put the logic into one OnlyAfterLearningPeriod with this approval function:\n\n\toverride public function approve():Boolean \n\t{\n\t\treturn (calendarModel.daysPassed \u003e config.numberOfNormalDaysAtStart)\n\t}\n\t\nWhich simplifies the actual Commands to:\n\n    override public function execute():void \n\t{\n\t\tlabourSurpriseEventCaster.castSurpriseEvent();\n\t}    \n\nAnd even better, makes my mapping more declarative of my intent:\n\n\tguardedCommandMap.mapGuardedEvent(DayCycleEvent.STONE_DELIVERY_COMPLETE, \n\t\t\t\t\t\t\t\t\t  OfferLabourCommand, \n\t\t\t\t\t\t\t\t\t  OnlyAfterLearningPeriod, \n\t\t\t\t\t\t\t\t\t  DayCycleEvent);\n\nAnd if I wanted to combine guards, I can supply an array of guards instead of a single class:\n\n\tguardedCommandMap.mapGuardedEvent(DayCycleEvent.STONE_DELIVERY_COMPLETE, \n\t\t\t\t\t\t\t\t\t  OfferLabourCommand,\n\t\t\t\t\t\t\t\t\t  [OnlyAfterLearningPeriod, OnlyWhenBehindSchedule], \n\t\t\t\t\t\t\t\t\t  DayCycleEvent);\n                                                         \n##Fallback Commands\n\nYou can also provide a fallback Command which will fire if the guards *don't* approve.\n\n\tguardedCommandMap.mapGuardedEventWithFallback(DayCycleEvent.DAY_COMPLETED, \n\t\t\t\t\t\t\t\t\t  ShowDayProgressBehindSchedule, // if the guard approves\n\t\t\t\t\t\t\t\t\t  ShowDayProgress, // if the guard doesn't approve\n\t\t\t\t\t\t\t\t\t  OnlyWhenBehindSchedule, \n\t\t\t\t\t\t\t\t\t  DayCycleEvent); \n              \n##Incorporating GuardedCommandMap into your robotlegs project\n\nYou just need to instantiate and map it in your context - either early in startup, or by overriding the mapInjections context method:\n\n\toverride protected function mapInjections():void\n\t{\n\t\tsuper.mapInjections();\n\t\tinjector.mapValue(IGuardedCommandMap, new GuardedCommandMap(eventDispatcher, injector, reflector));\n\t}\n\nThen just inject against IGuardedCommandMap in your other Commands.    \n\n##How do I create a Guard?\n\nThere is an optional interface - IGuard - to keep you honest, but any class which implements approve() and returns something will work, though you should be aware that the return value will be coerced to boolean.\n\nThe standard interface to implement is:\n\n\tfunction approve():Boolean;      \n\t\nOther than that there are no constraints on your guards, which means that if you're the sort of person who feels that lots of classes are a drag, you could hacky-hack your existing models to be guards - for example if you wanted to guard against something until a particular model has initialised. But I don't encourage that sort of thing. And you're probably using controllers anyway.\n\n\n##Anything else I need to know?\n\nThe full arguments list for mapping a guarded command is:\n\n\tfunction mapGuardedEvent(eventType:String, commandClass:Class, \n\t\t\t\t\t\t\t\tguards:*, eventClass:Class = null, oneshot:Boolean = false):void;\n\n\tfunction mapGuardedEventWithFallback(eventType:String, commandClass:Class, fallbackCommandClass:Class,\n\t\t\t\t\t\t\t\t\t\t\tguards:*, eventClass:Class = null, oneshot:Boolean = false):void;\n\t\nYou unmap using the normal unmapEvent() function from the standard CommandMap. This unmaps the event/command whether it was added with or without a fallback.\n\n\tfunction unmapEvent(eventType:String, commandClass:Class, eventClass:Class = null):void;\n\t\n\n##Compatibility with robotlegs versions\n\nThis util has been tested against robotlegs versions 1.0 and 1.4 - it should work for any. By simply including the 3 classes in the source of your project you can ensure it compiles against the same version of robotlegs that you're using.                  \n\n\n##Wot no swc\n\nTruth is, I can't get the damn thing to build a swc without also pulling in the robotlegs classes it extends, which would break compatibility with other versions of robotlegs. If you are a swc wizard, please fork and build a swc and share it.\n\n##Are these like Haskell Guards?\n\nKinda, maybe.\n\nThe inspiration for this CommandMap variation comes from the following blog post: http://blog.iconara.net/2008/03/30/separating-event-handling-from-event-filtering/\n\nThere is more discussion on guarding in general in the comments there.\n\nThanks to @AmyBlankenship for the suggestion.","funding_links":[],"categories":["Utilities"],"sub_categories":["Other Utilities"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FStray%2FGuardedCommandMap","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FStray%2FGuardedCommandMap","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FStray%2FGuardedCommandMap/lists"}