├── .gitattributes ├── .gitignore ├── main.lua ├── readme.md └── talkback.lua /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear on external disk 35 | .Spotlight-V100 36 | .Trashes 37 | 38 | # Directories potentially created on remote AFP share 39 | .AppleDB 40 | .AppleDesktop 41 | Network Trash Folder 42 | Temporary Items 43 | .apdisk 44 | -------------------------------------------------------------------------------- /main.lua: -------------------------------------------------------------------------------- 1 | local talkback = require 'talkback' 2 | 3 | -- listener adding and leaving 4 | do 5 | local testVariable = 1 6 | local testListener = talkback:listen('hi', function() 7 | testVariable = testVariable + 1 8 | end) 9 | talkback:say 'hi' 10 | 11 | assert(testVariable == 2, "test failed: listeners don't respond to group:say()") 12 | 13 | testListener:leave() 14 | talkback:say 'hi' 15 | assert(testVariable == 2, "test failed: listeners don't leave") 16 | end 17 | 18 | -- ignore and reset 19 | do 20 | local test1 = 1 21 | local test2 = 1 22 | local test3 = 1 23 | talkback:listen('hi', function() test1 = test1 + 1 end) 24 | talkback:listen('hi', function() test2 = test2 + 1 end) 25 | talkback:listen('hello', function() test3 = test3 + 1 end) 26 | talkback:say 'hi' 27 | talkback:say 'hello' 28 | 29 | assert(test1 == 2, "test failed: listeners don't respond to group:say()") 30 | assert(test2 == 2, "test failed: listeners don't respond to group:say()") 31 | assert(test3 == 2, "test failed: listeners don't respond to group:say()") 32 | 33 | talkback:ignore 'hi' 34 | talkback:say 'hi' 35 | talkback:say 'hello' 36 | 37 | assert(test1 == 2, "test failed: group:ignore() doesn't work") 38 | assert(test2 == 2, "test failed: group:ignore() doesn't work") 39 | assert(test3 == 3, "test failed: group:ignore() removes listeners it shouldn't remove") 40 | 41 | talkback:reset() 42 | talkback:say 'hi' 43 | talkback:say 'hello' 44 | 45 | assert(test1 == 2, "test failed: group:reset() doesn't work") 46 | assert(test2 == 2, "test failed: group:reset() doesn't work") 47 | assert(test3 == 3, "test failed: group:reset() doesn't work") 48 | end 49 | 50 | -- groups 51 | do 52 | local group = talkback:newGroup() 53 | local subGroup = group:newGroup() 54 | local test = 1 55 | talkback:listen('hi', function() test = test + 1 end) 56 | group:listen('hi', function() test = test + 1 end) 57 | subGroup:listen('hi', function() test = test + 1 end) 58 | 59 | talkback:say 'hi' 60 | assert(test == 4, "test failed: group:say() doesn't call nested groups correctly") 61 | group:say 'hi' 62 | assert(test == 6, "test failed: group:say() doesn't call nested groups correctly") 63 | subGroup:say 'hi' 64 | assert(test == 7, "test failed: group:say() doesn't call nested groups correctly") 65 | 66 | group:leave() 67 | talkback:say 'hi' 68 | assert(test == 8, "test failed: group:leave() doesn't work") 69 | 70 | group = talkback:newGroup() 71 | group:listen('hi', function() test = test + 1 end) 72 | talkback:say 'hi' 73 | assert(test == 10, "test failed: listeners don't respond to group:say()") 74 | talkback:reset() 75 | talkback:say 'hi' 76 | assert(test == 10, "test failed: talkback:reset() doesn't remove groups") 77 | 78 | group = talkback:newGroup() 79 | group:listen('hi', function() test = test + 1 end) 80 | talkback:say 'hi' 81 | assert(test == 11, "test failed: listeners don't respond to group:say()") 82 | talkback:ignore 'hi' 83 | talkback:say 'hi' 84 | assert(test == 11, "test failed: talkback:ignore() doesn't propogate to groups") 85 | end 86 | 87 | -- returns 88 | do 89 | local group = talkback:newGroup() 90 | local subGroup = group:newGroup() 91 | talkback:listen('hi', function() 92 | return 'somebody', 'once' 93 | end) 94 | group:listen('hi', function() 95 | return 'told', 'me', 'the' 96 | end) 97 | subGroup:listen('hi', function() 98 | return 'world', 'is', 'gonna', 'roll', 'me' 99 | end) 100 | 101 | local responses = {talkback:say 'hi'} 102 | assert(#responses == 10, "talkback:say doesn't return responses correctly") 103 | assert(responses[1] == 'somebody', "talkback:say doesn't return responses correctly") 104 | assert(responses[2] == 'once', "talkback:say doesn't return responses correctly") 105 | assert(responses[3] == 'told', "talkback:say doesn't return responses correctly") 106 | assert(responses[4] == 'me', "talkback:say doesn't return responses correctly") 107 | assert(responses[5] == 'the', "talkback:say doesn't return responses correctly") 108 | assert(responses[6] == 'world', "talkback:say doesn't return responses correctly") 109 | assert(responses[7] == 'is', "talkback:say doesn't return responses correctly") 110 | assert(responses[8] == 'gonna', "talkback:say doesn't return responses correctly") 111 | assert(responses[9] == 'roll', "talkback:say doesn't return responses correctly") 112 | assert(responses[10] == 'me', "talkback:say doesn't return responses correctly") 113 | 114 | local responses = {group:say 'hi'} 115 | assert(#responses == 8, "group:say() is returning responses from parent groups, somehow") 116 | assert(responses[1] == 'told', "group:say() is returning responses from parent groups, somehow") 117 | assert(responses[2] == 'me', "group:say() is returning responses from parent groups, somehow") 118 | assert(responses[3] == 'the', "group:say() is returning responses from parent groups, somehow") 119 | assert(responses[4] == 'world', "group:say() is returning responses from parent groups, somehow") 120 | assert(responses[5] == 'is', "group:say() is returning responses from parent groups, somehow") 121 | assert(responses[6] == 'gonna', "group:say() is returning responses from parent groups, somehow") 122 | assert(responses[7] == 'roll', "group:say() is returning responses from parent groups, somehow") 123 | assert(responses[8] == 'me', "group:say() is returning responses from parent groups, somehow") 124 | end 125 | 126 | print 'all tests passed' 127 | 128 | love = love or {} 129 | 130 | function love.keypressed(key) 131 | if key == 'escape' then 132 | love.event.quit() 133 | end 134 | end 135 | 136 | function love.draw() 137 | love.graphics.print 'all tests passed!' 138 | end -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | **Warning**: This library is deprecated, and I no longer recommend using it. I recommend [beholder](https://github.com/kikito/beholder.lua) or [hump-signal](https://github.com/vrld/hump) if you need a good observer pattern library. 2 | 3 | # Talkback 4 | 5 | **Talkback** is a small Lua library that lets you create "conversations". You can send messages from within a conversation, and you can create listeners that will do something (and optionally send back a response) when they receive a certain message. This is a powerful organizational tool that allows different parts of your code to interact with each other. 6 | 7 | ## Examples 8 | 9 | ### Player controls example 10 | 11 | ```lua 12 | -- in main.lua 13 | conversation = require 'lib.talkback' 14 | 15 | -- in player.lua 16 | conversation:listen('player jump', function() 17 | jump() 18 | end) 19 | 20 | -- in input-manager.lua 21 | if input.pressed('x') then 22 | conversation:say('player jump') 23 | end 24 | ``` 25 | 26 | ### High score display example 27 | 28 | ```lua 29 | -- in score-manager.lua 30 | conversation:listen('get high score', function(place) 31 | return scores[place] 32 | end) 33 | 34 | -- in hud.lua 35 | topScore = conversation:say('get high score') 36 | drawText(topScore) 37 | ``` 38 | 39 | ## Installation 40 | 41 | To use the library, place talkback.lua in the root folder of your project or in a subfolder, and load it with `require`. 42 | 43 | ```lua 44 | conversation = require 'talkback' -- if talkback.lua is in the root folder 45 | conversation = require 'path.to.talkback' -- if it's in a subfolder 46 | ``` 47 | 48 | ## Usage 49 | 50 | ### Listening for a message 51 | 52 | ```lua 53 | listener = conversation:listen(message, f) 54 | ``` 55 | 56 | Creates a new listener. The listener will execute a function `f` when `message` is sent. `message` can be any Lua value. 57 | - Returns a handle to the listener. Store this in a variable if you want to remove the individual listener later. 58 | 59 | ### Sending a message 60 | 61 | ```lua 62 | response1, response2, ... = conversation:say(message, ...) 63 | ``` 64 | 65 | Sends `message` to every listener and returns each listener's response. 66 | - '...' are arguments that will be passed to each listener's function 67 | 68 | #### Responses 69 | 70 | If a listener's function returns values, `conversation:say()` will return those values if the listener's function is called. All of the responses will be returned in the order that each listener returns them and in the order that the listeners were added. 71 | 72 | ```lua 73 | conversation:listen('lyrics', function() return 'some', 'body' end) 74 | conversation:listen('lyrics', function() return 'once', 'told', 'me' end) 75 | a, b, c, d, e = conversation:say('lyrics') 76 | print(a) -- prints "some" 77 | print(b) -- prints "body" 78 | print(c) -- prints "once" 79 | print(d) -- prints "told" 80 | print(e) -- prints "me" 81 | ``` 82 | 83 | If you're not sure how many responses you'll get, and you want to collect all of them, you can collect the responses in a table: 84 | 85 | ```lua 86 | lyrics = {conversation:say('lyrics')} 87 | -- lyrics is now the table {'some', 'body', 'once', 'told', 'me'} 88 | ``` 89 | 90 | ### Removing listeners 91 | 92 | ```lua 93 | listener:leave() 94 | ``` 95 | 96 | ### Removing all listeners for a certain message 97 | 98 | ```lua 99 | conversation:ignore(message) 100 | ``` 101 | 102 | ### Removing all listeners 103 | 104 | ```lua 105 | conversation:reset() 106 | ``` 107 | 108 | ### Groups 109 | 110 | You can create sub-groups from the main `conversation` group. When you say or ignore messages from inside a group, only that group (and child groups) will listen. Groups can be nested infinitely, and Talkback itself is a group. 111 | 112 | ```lua 113 | conversation = require 'talkback' 114 | 115 | conversation:listen('greeting', function() print 'hello' end) 116 | group = conversation:newGroup() 117 | group:listen('greeting', function() print 'world' end) 118 | 119 | conversation:say 'greeting' -- prints "hello" and "world" 120 | group:say 'greeting' -- only prints "world" 121 | group:ignore 'greeting' 122 | conversation:say 'greeting' -- now only prints "hello" 123 | ``` 124 | 125 | #### Creating new groups 126 | 127 | ```lua 128 | group = conversation:newGroup() 129 | subGroup = group:newGroup() 130 | -- etc. 131 | ``` 132 | 133 | #### Removing groups 134 | 135 | ```lua 136 | group:leave() 137 | ``` 138 | 139 | *Note:* don't call `group:leave()` on the main Talkback group, as this will cause an error, since that group doesn't have a parent. 140 | 141 | ## License 142 | 143 | MIT License 144 | 145 | Copyright (c) 2017 Andrew Minnich 146 | 147 | Permission is hereby granted, free of charge, to any person obtaining a copy 148 | of this software and associated documentation files (the "Software"), to deal 149 | in the Software without restriction, including without limitation the rights 150 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 151 | copies of the Software, and to permit persons to whom the Software is 152 | furnished to do so, subject to the following conditions: 153 | 154 | The above copyright notice and this permission notice shall be included in all 155 | copies or substantial portions of the Software. 156 | 157 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 158 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 159 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 160 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 161 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 162 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 163 | SOFTWARE. 164 | -------------------------------------------------------------------------------- /talkback.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | MIT License 3 | 4 | Copyright (c) 2017 Andrew Minnich 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | ]] 24 | 25 | local unpack = unpack or table.unpack 26 | 27 | local function filter(t, f) 28 | for i = #t, 1, -1 do 29 | if f(t[i]) then 30 | table.remove(t, i) 31 | end 32 | end 33 | end 34 | 35 | local function remove(t, v) 36 | for i = #t, 1, -1 do 37 | if t[i] == v then 38 | table.remove(t, i) 39 | break 40 | end 41 | end 42 | end 43 | 44 | local Listener = {} 45 | 46 | function Listener:leave() 47 | remove(self._parent._listeners, self) 48 | end 49 | 50 | local Group = {} 51 | 52 | function Group:listen(message, f) 53 | local listener = setmetatable({ 54 | _message = message, 55 | _f = f, 56 | _parent = self, 57 | }, {__index = Listener}) 58 | table.insert(self._listeners, listener) 59 | return listener 60 | end 61 | 62 | function Group:say(message, ...) 63 | local responses = {} 64 | for _, listener in pairs(self._listeners) do 65 | if listener._message == message then 66 | for _, response in ipairs {listener._f(...)} do 67 | table.insert(responses, response) 68 | end 69 | end 70 | end 71 | for _, group in ipairs(self._groups) do 72 | for _, response in ipairs {group:say(message, ...)} do 73 | table.insert(responses, response) 74 | end 75 | end 76 | return unpack(responses) 77 | end 78 | 79 | function Group:leave() 80 | if self._parent == Group then 81 | error "can't call :leave() on a group without a parent" 82 | end 83 | remove(self._parent._groups, self) 84 | end 85 | 86 | function Group:ignore(message) 87 | for _, group in ipairs(self._groups) do 88 | group:ignore(message) 89 | end 90 | filter(self._listeners, function(l) 91 | return l._message == message 92 | end) 93 | end 94 | 95 | function Group:reset() 96 | self._groups, self._listeners = {}, {} 97 | end 98 | 99 | function Group:newGroup() 100 | local group = setmetatable({ 101 | _listeners = {}, 102 | _groups = {}, 103 | _parent = self, 104 | }, {__index = Group}) 105 | if self ~= Group then 106 | table.insert(self._groups, group) 107 | end 108 | return group 109 | end 110 | 111 | return Group:newGroup() --------------------------------------------------------------------------------