Changes between Version 20 and Version 21 of signals


Ignore:
Timestamp:
04/16/2021 08:58:13 AM (20 months ago)
Author:
marcel.taeumel
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • signals

    v20 v21  
    1 [[PageOutline(1-3)]]
    2 
    3 = Signals =
    4 
    5 A simple callback is a function call that is performed when another action took place. Normally, callback can be registered to an object and will be processed by that object whenever the time is right. There are several ways to implement such callbacks. Some are more general, others only apply to a subset of possible actions in the system. The ''Signals'' implementation tries to be usable in all cases but adds one or another constraint to keep code that uses signals readable.
    6 
    7 = How to Install =
    8 
    9 {{{
    10 #!div class="wiki_infotable" style="float:right;"
    11 ||'''Environment'''|| ||
    12 || [[Image(media/icons/custom:squeak_16.png, title="Recommended Squeak Version", nolink, right)]] || 4.1, 4.2 and later ||
    13 || [[Image(media/icons/silk:application_home.png, title="Recommended Squeak VM Version", nolink, right)]] || 4.1.1 (Win) ||
    14 || [[Image(media/icons/silk:cog.png, title="Recommended Cog VM Version", nolink, right)]] || r2316 ||
    15 ||'''Sources'''|| ||
    16 || [[Image(media/icons/silk:script_gear.png, title="Metacello Configuration", nolink, right)]] || [http://www.hpi.uni-potsdam.de/hirschfeld/squeaksource/MetacelloRepository/ ConfigurationOfSignals] ||
    17 || [[Image(media/icons/silk:database.png, title="Repository", nolink, right)]] || [http://www.hpi.uni-potsdam.de/hirschfeld/squeaksource/SwaUtilities/ SwaUtilities] ||
    18 
    19 || [[Image(media/icons/silk:package.png, title="Needed Packages from the Repository", nolink, right)]] || `Signals` ||
    20 || [[Image(media/icons/silk:package.png, title="Needed Packages from the Repository", nolink, right)]] || `SI-Wrapper` (optional) ||
    21 || [[Image(media/icons/silk:package.png, title="Needed Packages from the Repository", nolink, right)]] || `SI-OB-Morphic` (optional) ||
    22 || [[Image(media/icons/silk:package.png, title="Needed Packages from the Repository", nolink, right)]] || `SI-Reflection` (optional) ||
    23 || [[Image(media/icons/silk:package.png, title="Needed Packages from the Repository", nolink, right)]] || `SI-Benchmarks` (optional) ||
    24 
    25 || [[Image(media/icons/silk:bullet_go.png, title="Dependents", nolink, right)]] || [http://www.squeaksource.com/OmniBrowser.html OmniBrowser] (optional) ||
    26 || [[Image(media/icons/silk:bullet_go.png, title="Dependents", nolink, right)]] || [wiki:methodwrappers Method Wrappers] (optional) ||
    27 || [[Image(media/icons/silk:bullet_go.png, title="Dependents", nolink, right)]] || [http://www.squeaksource.com/AXAnnouncements.html AXAnnouncements] (optional) ||
    28 
    29 ||'''Misc'''|| ||
    30 || [[Image(media/icons/silk:world.png, title="Website", nolink, right)]] || [http://www.hpi.uni-potsdam.de/hirschfeld/squeaksource/SwaUtilities.html SwaUtilities@SqueakSource] ||
    31 }}}
    32 
    33 Using Metacello, just run the following code in your workspace:
    34 {{{
    35 (Installer mc http: 'http://www.hpi.uni-potsdam.de/hirschfeld/squeaksource/')
    36    project: 'MetacelloRepository';
    37    install: 'ConfigurationOfSignals'.
    38 ConfigurationOfSignals load.
    39 }}}
    40 
    41 If you later want to get the latest development version of Signals, run this:
    42 
    43 {{{
    44 ConfigurationOfSignals loadDevelopment.
    45 }}}
    46 
    47 The ''Signals'' project contains several sub-packages:
    48 
    49  * `Signals-Core` ... main signals implementation
    50  * `Signals-Tests` ... tests for the core implementation
    51  * `SI-OB-Morphic` ... morphic extensions to `OmniBrowser`
    52  * `SI-Benchmarks` ... benchmmarks for Signals and other mechanisms, e.g., Announcements
    53  * `SI-Wrapper` ... turns each message into a signal using method wrappers
    54  * `SI-Reflection` ... search and browse signals in the image
    55 
    56 {{{
    57 #!div style="clear:both;"
    58 }}}
    59 
    60 '''Important Notes''' for development version users: Support for [#WrappedSignals wrapped signals] removed in `Signals-mt.32`. So there is no need to load `SI-Wrapper´ right now.
    61 
    62 = How to Use =
    63 
    64 == Terms ==
    65 
    66 A ''signal'' is a method that can be called to trigger a callback. Then the signal will be ''emitted''.
    67 
    68 A ''callback'' is the method that is called or the process that is involved in performing this method call in response to an emitted signal.
    69 
    70 A ''connection'' is a pair of signal and callback. The pair is used to lookup all callbacks whenever a signal is emitted. A signal can have multiple connections.
    71 
    72 == Implementing Signals ==
    73 
    74 A signal is an object's method that is able to trigger callbacks. It is implemented like this:
    75 
    76 {{{
    77 MyCounter>>valueChanged: newValue
    78    self emit.
    79 }}}
    80 
    81 Each signal can have an arbitrary number of arguments. The emit-call triggers the callback. In this way, any method could be used as a signal. This could be very confusing for the developer, therefore it is not recommended. Calling `self emit` not inside a method context but a block context works as well.
    82 
    83 Obviously, a signal is emitted by calling the method that is meant to be the signal:
    84 
    85 {{{
    86 self valueChanged: 42.
    87 }}}
    88 
    89 You '''must not''' emit an object's signal from the outside. The following fails if not executed within the same instance as `myCounter`:
    90 
    91 {{{
    92 myCounter valueChanged: 42.
    93 }}}
    94 
    95 One could argue that this is an unnecessary constraint, but it is the idea of the signals, that an object decides itself when a signal is emitted. Allowing the signal sending from everywhere could lead to less readable code.
    96 
    97 See the [#AdvancedUsage advanced usage] section and read about [#PublicSignals public signals] if you want to bypass this constraint for some reason.
    98 
    99 == Basic Connections ==
    100 
    101 The simplest form of a connection is created from within the UI process of Squeak, e.g., using the workspace, in the following way:
    102 
    103 {{{
    104 self
    105    connect: myCounter
    106    signal: #valueChanged:
    107    to: Transcript
    108    selector: #show:.
    109 }}}
    110 
    111 
    112 This more or less mediator approach can be changed if an object itself connects to a signal:
    113 
    114 {{{
    115 self
    116    connect: myCounter
    117    signal: #valueChanged:
    118    toSelector: #show:.
    119 }}}
    120 
    121 '''Note on garbage collection:''' You need to store a reference to the receiver of the callback. Otherwise the garbage collector will collect the receiver and the connection is lost.
    122 
    123 == Basic Signal Processing ==
    124 
    125 In the simplest way, a signal is emitted from within the UI process of Squeak, e.g., in response to a Morphic `#mouseDown:` event:
    126 
    127 {{{
    128 "The signal."
    129 MyMorph>>mouseDownReceived: evt
    130    self emit.
    131    
    132 "The default event handling routine in Morphic."
    133 MyMorph>>mouseDown: evt
    134    self mouseDownReceived: evt.
    135 }}}
    136 
    137 
    138 Having this, the signal is processed synchronously and blocking, which means that all callbacks were made after returning from the call that emitted the signal.
    139 
    140 == Arguments and Patterns ==
    141 
    142 Each callback should expect at most arguments as the signal contains. By default, trailing arguments will be truncated and ignored:
    143 
    144 {{{
    145 "Works."
    146 self connect: s signal: #a: toSelector: #b:.
    147 "Works. Auto-truncate."
    148 self connect: s signal: #a: toSelector: #c.
    149 "Does not work."
    150 self connect: s signal: #a toSelector: #b:.
    151 }}}
    152 
    153 It is possible to choose and reorder specific arguments from the signal with a ''pattern''. A pattern is an array of numbers that maps argument positions. It must have exactly as many elements as the callback needs:
    154 
    155 {{{
    156 self
    157    connect: aSender
    158    signal: #a:b:c:
    159    toSelector: #d:e:f:
    160    pattern: #(3 1 2). "c->d, a->e, b->f"
    161    
    162 self
    163    connect: aSender
    164    signal: #a:b:c:
    165    toSelector: #d:e:f:
    166    pattern: #(4 1). "Error!"
    167 }}}
    168 
    169 The automatic truncation can be used to avoid patterns like `#(1 2)` or `#()`. Arguments can be duplicated, i.e., `#(1 1)`.
    170 
    171 == Disconnection ==
    172 
    173 A connection can be removed from the sender:
    174 
    175 {{{
    176 self "the sender"
    177    disconnectSignal: #valueChanged:
    178    from: aReceiver
    179    selector: #processValue:.
    180 }}}
    181 
    182 
    183 You can also remove all connections from a specific receiver, all connections from a specific signal and any connection that was ever created.
    184 
    185 Although, connections will be destroyed if either sender or receiver is deleted automatically, sometimes it could be useful to disconnect a signal to avoid endless loops.
    186 
    187 
    188 == Using Processes ==
    189 
    190 The connection and disconnection of signals is thread-safe.
    191 
    192 Signals can be emitted from within any Squeak process. Normally, the creator of a connection should not bother where the signal is emitted but where the callback is processed to avoid considering thread-safety of involved data structures.
    193 
    194 When creating a connection from within the Squeak UI process, callbacks will be processed within that process no matter where the signal was emitted. This is a special case of so called ''queued connections''.
    195 
    196 
    197 === Queued Connections ===
    198 
    199 Sometimes it is necessary to specify the process that is involved during signal processing. Except for the UI process, any connection made from within any other process will be handled in the process where the signal is emitted. This could lead to unexpected behavior if all involved data structures are not thread-safe!
    200 
    201 {{{
    202 "Here: Make you data structures thread-safe!"
    203 [anObject
    204    connect: myCounter
    205    signal: #valueChanged:
    206    toSelector: #processValue:] fork.
    207 }}}
    208 
    209 
    210 To encounter this problem, connections can be queued:
    211 
    212 {{{
    213 Transcript
    214    connect: myCounter
    215    signal: #valueChanged:
    216    toSelector: #show:
    217    queue: aSharedQueue.
    218 }}}
    219 
    220 
    221 Using a queue, an emitted signal causes the queue to be filled with the callbacks stored into blocks that have to be evaluated by a process frequently.
    222 
    223 The Squeak UI process has such a queue already that will be processed in the main world cycle frequently: `WorldState>>deferredUIMessages`. Using this, basic connections from within the UI process will be queued if the signal is emitted from within any other process than the UI process automatically. Otherwise they are processed synchronously and blocking.
    224 
    225 Any queued connection can be blocking which means that the signal emitting process will be suspended until the callback waiting in the queue is processed:
    226 
    227 {{{
    228 Transcript
    229    connect: myCounter
    230    signal: #valueChanged:
    231    toSelector: #show:
    232    queue: aSharedQueue
    233    blocking: true.
    234 }}}
    235 
    236 
    237 Of course, anyone can resume a suspended process in Squeak, but this implementation of blocking connections was quite simple and should work for the most cases.
    238 
    239 
    240 === Avoiding Deadlocks ===
    241 
    242 When working with queued connections, deadlocks can appear, e.g., if a process waits for a signal to be processed by the same process that normally looks for the queue frequently. This can happen especially in the UI process and fixed as follows:
    243 
    244 {{{
    245 [self signalWasSent "non-blocking"]
    246    whileFalse: [
    247       WorldState deferredUIMessages next value "blocking"].
    248 }}}
    249 
    250 This approach works with any queue. It is similar to `processEvents()` in the Qt Framework. In Squeak, the whole world can be kept responsive by calling `World>>doOneCycle` frequently.
    251 
    252 If you create a blocking connection and a signal is emitted with a queued callback to be processed in the same process, nothing will happen until the process is resumed from the outside. You should not do that.
    253 
    254 
    255 === Waiting for Signals ===
    256 
    257 The `SignalSpy` can be used to wait for signals explicitly. Normally, it should be used in tests and not in the application itself:
    258 
    259 {{{
    260 spy := SignalSpy
    261    onSender: aSender
    262    signal: #signalEmitted.
    263 signal := spy waitForNextSignal.
    264 }}}
    265 
    266 
    267 The signal spy catches all registered signals emitted from any process. Wait operations are blocking so be sure that the signals are emitted in another process. You can wait for a specific signal using: `#waitForSignal:`.
    268 
    269 If you want to test for some properties of the next signal besides its name, use `#waitForNextSignalSatisfying:`:
    270 
    271 {{{
    272 spy waitForNextSignalSatisfying: [:signal | |arguments|
    273    arguments := signal second.
    274    arguments first = #foobar].
    275 }}}
    276 
    277 
    278 The structre stored by the `SignalSpy` is an array with two fields: signal name and signal arguments.
    279 
    280 == Awareness ==
    281 
    282 There is an extension to the OmniBrowser Framework that shows a small icon in front of signals in the message list:
    283 
    284   [[Image(SignalsOmniBrowserMorphic.PNG, title="Signals have a custom icon in the message list.", nolink)]]
    285 
    286 Trying to load the Signals package into a Squeak image without OmniBrowser installed results in a warning that can be ignored safely.
    287 
    288 A connection creation needs symbols - one for the signal and one for the callback. Therefore it is possible to use the Squeak ''Browse Senders'' way to investigate them:
    289 
    290 {{{
    291 self
    292    connect: aSender
    293    signal: #valueChanged: "Will be found."
    294    to: aReceiver
    295    selector: #value:. "Will be found."
    296 }}}
    297 
    298 
    299 Unfortunately the signal emitting place, which is just a message send, and other message sends that are not callbacks will be found as well. To solve this problem, some reflective methods were added to `SystemNavigation` to allow browsing for connections and signal sends:
    300 
    301 {{{
    302 SystemNavigation default
    303    allConnectionsOn: #valueChanged:
    304    from: aClass. "Here is the connection code
    305                  somewhere."
    306    
    307 SystemNavigation default
    308    allSignalEmitsFrom: aSenderClass
    309    signal: #valueChanged:.
    310 }}}
    311 
    312 
    313 == Advanced Usage ==
    314 
    315 === Public Signals ===
    316 
    317 To bypass the constraint that a signal cannot be emitted from the outside but must be emitted from the instance that has the signal, you can use ''public signals''. These types of signals just do not have that constraint and are implemented as the following:
    318 
    319 {{{
    320 MyCounter>>valueChanged: newValue
    321    self emitAlways.
    322 }}}
    323 
    324 Use them with caution because they could lead to less readable code.
    325 
    326 
    327 === Connect Signal to Signal ===
    328 
    329 Signals are just normal methods. Therefore they can be used as callbacks as well. If the receiver is not identical to the sender of the signal you need to use a ''public signal'' as target:
    330 
    331 {{{
    332 self
    333    connect: aSender
    334    signal: #valueChanged "Signal #1"
    335    toSelector: #switchToggled. "Signal #2 - Has to be public if sender <> receiver!"
    336 }}}
    337 
    338 
    339 === Automatic Connections ===
    340 
    341 It is possible to create methods that look like they want to be connected to a senders signal:
    342 
    343 {{{
    344 MyDialog>>onNameEditTextChanged: aText
    345    self availabilityLabel contents:
    346       (self checkAvailability: aText).
    347 }}}
    348 
    349 
    350 Such a method can benefit from its signature if there is an instance variable named `nameEdit` which sends the signal `#textChanged:`. Automatic connections can be created from such patterns after the referenced instance variables were initialized:
    351 
    352 {{{
    353 MyDialog>>initialize
    354    super initialize.
    355    nameEdit := MyNameEdit new.
    356    self createAutoSignalConnections.
    357 }}}
    358 
    359 
    360 If the sender does not send this signal the connection will silently not be created.
    361 
    362 === Wrapped Signals ===
    363 
    364 Sometimes it could be useful to re-use a arbitrary message that an object understands as a signal, e.g., to be notified if a morph changes its position without having to subclass that morph like this:
    365 
    366 {{{
    367 MyMorph>>position: aPosition
    368    super position: aPosition.
    369    self emitAlways. "Would have to be a public signal."
    370 }}}
    371 
    372 
    373 Or like this:
    374 
    375 {{{
    376 MyMorph>>position: aPosition
    377    super position: aPosition.
    378    self positionChanged: aPosition.
    379 
    380 MyMorph>>positionChanged: newPosition
    381    self emit.
    382 }}}
    383 
    384 
    385 To avoid this, it is possible to use '''any arbitrary message''' as a signal as long has the sub-package ''SI-Wrapper'' is installed which needs the package/project [wiki:methodwrappers Method Wrappers] as well:
    386 
    387 {{{
    388 "Installs a method wrapper on #position:."
    389 self connect: aMorph signal: #position: to: Transcript selector: #show:.
    390 }}}
    391 
    392 
    393 There will be only one method wrapper installed for each message that will be used in a connection for the first time. If no connections use that message as a signal, the wrapper will be removed from that message.
    394 
    395 === Advanced Patterns ===
    396 
    397 It is possible to access the sender of a signal itself and use it as an argument for the receiver. This is achieved with a `0` in the pattern and can reduce the number of needed methods because the control flow may consider the sender, e.g. different actions are triggered and show themselves on a log:
    398 
    399 {{{
    400 self
    401    connect: anAction
    402    signal: #triggered
    403    toSelector: #logAction:
    404    pattern: #(0).
    405 
    406 self
    407    connect: anotherAction
    408    signal: #triggered
    409    toSelector: #logAction:
    410    pattern: #(0).
    411 }}}
    412 
    413 Patterns can have default values that will be send if the signal is processed:
    414 
    415 {{{
    416 "self changed: #counterValue."
    417 self
    418    connect: myCounter
    419    signal: #valueChanged:
    420    toSelector: #changed:
    421    pattern: #(=counterValue).
    422 }}}
    423 
    424 You can send whole objects as default value:
    425 
    426 {{{
    427 self
    428    connect: myCounter
    429    signal: #valueChanged:
    430    toSelector: #addMorphBack:
    431    pattern: {#=. Morph new}.
    432 }}}
    433 
    434 Patterns can mix index references to the sender's arguments and default values: `#(2 1 =foobar)`. As you see, the magic happens just because of the escape symbol `#=`. Every value that comes after it, will be treated as itsself and not as an index reference.
    435 
    436 '''Hint:''' If you store an object into a pattern, the garbage collector will collect that object if it is not referenced somewhere else. In that case, ''nil'' will be supplied as argument.
    437 
    438 = How to Extend =
    439 
    440 == General Implementation Approach ==
    441 
    442  * one central repository `SignalConnectionsRepository` stores all connections in the system
    443  * `self emit` looks into the call-stack to retrieve arguments and do other checks
    444  * the repository uses ''weak'' data structures to not interfere with the garbage collector
    445 
    446 == Next Possible Steps ==
    447 
    448  * ''arguments processor'' to transform arguments before doing the callback
    449  * detect recursions on connection level to prevent endless loops, e.g., count and reset a number in each connection
    450  * Test-Runner coverage testing is broken because signals will not be emitted in the correct context and fail
    451 
    452 
    453 = Other Observer Mechanisms =
    454 == Object Dependents ==
    455 
    456 Every object in Squeak has a list of dependent objects. You trigger an event with `#changed:` or `#changed:with:` and all dependents receive a `#update:` resp. `#update:with:` call. Dependents are managed using `#addDependent:` and `#removeDependent:`.
    457 
    458 Squeak processes will not be considered and data structures have to secured to avoid synchronization problems. Using this mechanism, objects are connected completely and not only in special cases because there is no filtering possible.
    459 
    460 ''Note on performance issues:'' As you see in the benchmarks, setting up thousands of bindings is quite slow. Therefore you should subclass `Model` instead of `Object` if you want to write faster code. `Model` uses a custom implementation of ''dependents''. But it is not possible for Morphs because they subclass from `Object` directly.
    461 
    462 == Object Events ==
    463 
    464 Squeak has a built-in callback mechanism that is lightweight and decouples sender and receiver in a convenient way.
    465 
    466 {{{
    467 self "sender"
    468    when: #valueChanged
    469    send: #value:
    470    to: aReceiver.
    471 }}}
    472 
    473 
    474 However, events can be triggered from everywhere and new event names can be made up at any time so that that it can be confusing to the developer to realize all possible events from an object:
    475 
    476 {{{
    477 self "sender"
    478    triggerEvent: #valueChanged
    479    with: 42.
    480 
    481 "Surprisingly new event."
    482 self
    483    triggerEvent: #surprised
    484    with: 23.
    485 
    486 "Some other object triggers my event. Strange..."
    487 MyConfuser>>confuseCounter
    488    myCounter
    489       triggerEvent: #valueChanged
    490       with: 42.
    491 }}}
    492 
    493 Although, it is possible to handle events that no one processes separately.
    494 
    495 == Morphic Callbacks ==
    496 
    497 Default mouse or keyboard input events can be connected using `#on:send:to:`. This avoids the need to implement, e.g., `#mouseDown:`, or `#keyStroke:` but messages with more readable names.
    498 
    499 This approach is only limited to these standard events and cannot be used so create arbitrary connections.
    500 
    501 == Announcements ==
    502 
    503 [[Image(media/icons/silk:world.png)]] [http://www.squeaksource.com/AXAnnouncements.html AXAnnouncements@SqueakSource]
    504 
    505 == Bindings ==
    506 
    507 [http://www.hpi.uni-potsdam.de/hirschfeld/squeaksource/Bindings.html Bindings@SqueakSource]
    508 
    509 == Feature Comparison ==
    510 
    511 Every callback implementation has another notion of the terms involved. For clarification, we use ''Event'' as the thing that can be triggered in notion of a ''Sender'' and a ''Callback'' that is processed in notion of a ''Receiver'' if the necessary ''Binding'' was configured correctly.
    512 
    513 {{{
    514 #!div class="wiki_table"
    515 ||                                  || Morphic[[br]]Callbacks || Announce-[[br]]ments || Object[[br]]Dependents || Object[[br]]Events || Bindings ||  Signals ||
    516 || Allows definiton of new Events ||                          || ● || ● || ● || ? || ● ||
    517 || Available Events are defined explicitly ||               ● || ● ||   ||   || ? || ● ||
    518 || Events are defined for a specific class/subclass ||        ||   ||   ||   || ? || ● ||
    519 || Bindings are at Event level ||                           ● || ● ||   || ● || ? || ● ||
    520 || Bindings are at !Sender/Receiver level ||                   ||    || ● ||   || ? ||   ||
    521 || Events are first-class objects ||                          || ● ||   ||   || ? ||   ||
    522 || Automatic truncation of arguments ||                     ● ||   ||   || ● || ? || ● ||
    523 || Explicit argument count check at binding-setup-time ||     ||   ||   ||   || ? || ● ||
    524 || Explicit argument count check at event-trigger-time ||     || ● ||   ||   || ? ||   ||
    525 || Access to sending object at binding-level ||               ||   ||   ||   || ? || ● ||
    526 || Access to sending object at event-level ||                 || ● || ● || ● || ? || ● ||
    527 || Event encodes number of available arguments ||             || ● ||   ||   || ? || ● ||
    528 || Unhandled Events can be processed separately ||            ||   ||   || ● || ? ||   ||
    529 || Event triggering can be limited to the sender ||           ||   ||   ||   || ? || ● ||
    530 || Provides synchronisation mechansism for thread-safety ||   ||   ||   ||   || ? || ● ||
    531 || Explicit !Event/Callback check at binding-setup-time ||     ||   ||   ||   || ? || ● ||
    532 }}}
    533 
    534 ''It is not guaranteed that the table is correct except for the Signals column.''
    535 
    536 == Benchmark ==
    537 
    538 One sender was bound to 10000 receivers. Then one event was triggered and processed synchronously. The benchmark code is in the ''Signals'' package.
    539 
    540  '''Test-System:''' Core2Duo @ 2.54 GHz, 4096 MB DDR2 RAM, Windows 7 Professional, Squeak 4.1
    541 
    542 {{{
    543 #!div class="wiki_table"
    544 ||                        || Message[[br]]Sends || Announce-[[br]]ments || Object[[br]]Dependents     || Object[[br]]Events || Bindings || Signals ||
    545 || Bindings creation time || 0 ms[[br]]'''0 %'''|| 44 ms[[br]]'''20 %'''|| 39765 ms[[br]]'''17832 %'''|| 64204 ms[[br]]'''28791 %'''|| ?|| 223 ms[[br]]'''100 %'''||
    546 || Event triggering time  || 1 ms[[br]]'''4 %'''|| 4 ms[[br]]'''17 %'''|| 2 ms[[br]]'''8 %'''         || 78 ms[[br]]'''325 %''' ||     ?|| 24 ms[[br]]'''100 %'''||
    547 }}}
    548 
    549 
    550 = Acknowledgments =
    551 
    552 The signals mechanism was inspired by the signals/slots concept in the [http://qt.nokia.com Nokia Qt Framework].
    553 
    554 [[Image(media/icons/silk:user.png, title="Target Group", nolink)]] To date the following people contributed to this project:
    555  * Marcel Taeumel
     1This project, including all sources and documentation, moved to [https://github.com/hpi-swa/signals]