October 17, 2021, 10:33:18

Author Topic: [bmx] Notification Center by N [ 1+ years ago ]  (Read 665 times)

Offline BlitzBot

  • Jr. Member
  • **
  • Posts: 1
[bmx] Notification Center by N [ 1+ years ago ]
« on: June 29, 2017, 00:28:42 »
Title : Notification Center
Author : N
Posted : 1+ years ago

Description : This code is based off the API provided by NSNotificationCenter in Cocoa.  Because I really like Cocoa.  What it does is provides a sort of convenient way to pass messages between threads, as well as a method of notifying objects of changes/that something is happening/your pants are wet without having to tell the notifying object which objects they are and what type they are, since it doesn't matter.

I tried to make the TNotificationCenter type thread-safe, but given that I'm still new to this whole malarky, there may be things I've missed.  It should hopefully be impossible to cause a deadlock, at least, unless you mess with the center's fields yourself, which you shouldn't be doing.  They say "Restricted" for a reason.

Anyhow, here's an example, since this came up recently (as of 5/7/09, anyhow). This code loads a pixmap in the background and queues a notification to notify the object (in the main thread) when the image is loaded.
Code: [Select]
SuperStrict

Import "notification.bmx"

Type th_LoadImageInfo
Field url:Object

' Thread proc
Function th_LoadImageWithPixmap:Object(data:Object)
Delay 5000 ' Just so you can see the pretty loading icon

' Get the notification center for the main thread
Local nc:TNotificationCenter = TNotificationCenter.DefaultCenterForMainThread()
Try
' Get the info for the image
Local info:th_LoadImageInfo = th_LoadImageInfo(data)
' Load the pixmap
Local pix:TPixmap = LoadPixmap(info.url)
' Queue a notification for the other thread
nc.EnqueueNotificationWithName("ImageLoadNotification", data, pix)
Catch o:Object
' Or, if there was an exception, queue that as the notification
' and let the main thread deal with it when it gets around to it
nc.EnqueueNotificationWithName("ImageLoadNotification", data, o)
End Try
' Done
Return Null
End Function

Method Load()
' Create a thread to load the pixmap
TThread.Create(th_LoadImageWithPixmap, Self).Detach()
End Method
End Type

Type Entity
Field _image:TImage

' Method called when the notification is posted
Method ImageLoader:Int(n:TNotification)
' Remove Self as an observer of the ImageLoadNotification notification
TNotificationCenter.DefaultCenter().RemoveObserver(Self, "ImageLoadNotification")

' Check to see if the info passed with the notification is a pixmap
If TPixmap(n.UserInfo()) Then
' if it is, load the image
_image = LoadImage(n.UserInfo())
Else
' otherwise, it's an exception
Throw n.UserInfo()
EndIf
End Method

Method Draw( x:Int, y:Int )
If _image Then
' Draw the image
SetBlend(SOLIDBLEND)
SetColor(255,255,255)

SetRotation(0)
SetOrigin(0,0)
SetHandle(0,0)

DrawImage(_image,x,y)
Else
' Draw a loading icon while the image is loading
'#region Ugly loading icon code stuff
SetHandle(20,20)
SetOrigin(x, y)

' Draw border circle/inner circle
SetBlend(SOLIDBLEND)

SetColor(80,80,80)
DrawOval(0,0,40,40)

SetColor(48,48,48)
DrawOval(2,2,36,36)

' Draw loading icon thing
SetBlend(LIGHTBLEND)
SetColor(255,255,255)
SetHandle(2, -8)

Local time:Int = Int(Millisecs() * .01) Mod 8
Local c:Float

For Local i:Int = 0 Until 8
c = 255-(20+((((time+i) Mod 8)*32)*.859375))

SetRotation(i*-45)
SetColor(c,c,c)
DrawRect(0, 0, 4, 6)
Next

' Reset
SetRotation(0)
SetHandle(0, 0)
SetOrigin(0, 0)
SetBlend(SOLIDBLEND)

'#endregion
EndIf
End Method

Method ExecLoadImage()
' Thread info/data
Local info:th_LoadImageInfo = New th_LoadImageInfo
info.url = "image.jpg"
' Get the default notification center
Local nc:TNotificationCenter = TNotificationCenter.DefaultCenter()
' Add Self as an observer of the ImageLoadNotification
nc.AddObserver(Self, "ImageLoader", "ImageLoadNotification", info)
' Begin loading the image in another thread
info.Load()
End Method
End Type

Graphics 800,600,0,0
'buildopt:threads
'buildopt:gui
' ^^^ my special tags for building things

' create an entity and tell it to start loading the image
Local e:Entity = New Entity
e.ExecLoadImage()

Local Running:Int = True
While Running
While PollEvent()
If CurrentEvent.id = EVENT_APPTERMINATE Or (CurrentEvent.id = EVENT_KEYDOWN And CurrentEvent.data = KEY_ESCAPE) Then
Running = False
EndIf
Wend

' Process queued notifications for the current thread
TNotificationCenter.Process()

' Drawing
Cls

' draw the entity - it'll either be the loading icon or the image, either
' way this thread is still going all the time
e.Draw( 400+Sin(Millisecs()*.03)*320, 32 )

Flip

Wend

EndGraphics
End


Most of the documentation is contained in the Rem:doc..EndRem blocks (unfortunately, BBDoc doesn't handle them, and my documentation generator isn't finished, so you'll have to make do with reading the code), so for specific details about methods, refer to that.  Generally, though, all you'd normally use are the DefaultCenter, DefaultCenterForMainThread, PostNotificationWithName, and EnqueueNotificationWithName methods.

PUFFINS [/i]

Code :
Code: BlitzMax
  1. SuperStrict
  2.  
  3. ?Threaded
  4. ' Doesn't really affect anything important to wrap it in ?Threaded..?, but it
  5. ' does remove on addition function call at startup
  6. Import Brl.Threads
  7. ?
  8. Import Brl.LinkedList
  9. Import Brl.Reflection           ' For notifying objects
  10.  
  11. Private
  12.  
  13. Rem:doc
  14.         Internal type for storing information about notification observers.
  15. EndRem
  16. Type TNotificationObserver {Immutable}
  17.         Field name$ {ReadOnly}
  18.         Field observer:Object {ReadOnly}
  19.         Field invocation:TMethod {ReadOnly}
  20.         Field forObject:Object {ReadOnly}
  21. End Type
  22.  
  23. Public
  24.  
  25. Type TNotification {Immutable}
  26.         Field _name:String {ReadOnly}
  27.         Field _object:Object {ReadOnly}
  28.         Field _userinfo:Object {ReadOnly}
  29.        
  30.         Rem:doc
  31.                 Initializes the notification with a {param:name},
  32.                 {param:associated object|obj}, and {param:user-info object|info}.
  33.                 @param:name The name of the notification.
  34.                 @param:obj The object associated with the notification.
  35.                 @param:info User info that accompanies the notification.
  36.                 @returns The notification.
  37.         EndRem
  38.         Method InitWithName:TNotification(name$, obj:Object, info:Object)
  39.                 name = name.Trim()
  40.                 Assert name, "Cannot initialize notification with empty name"
  41.                 _name = name
  42.                 _object = obj
  43.                 _userinfo = info
  44.                 Return Self
  45.         End Method
  46.        
  47.         Rem:doc
  48.                 Makes a copy of the target notification.
  49.                 @return A copy of the target notification.
  50.         EndRem
  51.         Method Copy:TNotification()
  52.                 Return New TNotification.InitWithName(_name, _object, _userinfo)
  53.         End Method
  54.        
  55.         Rem:doc
  56.         Returns the name of the notification.
  57.         EndRem
  58.         Method Name$()
  59.                 Return _name
  60.         End Method
  61.        
  62.         Rem:doc
  63.         Returns the object associated with the notification.
  64.         EndRem
  65.         Method AssociatedObject:Object()
  66.                 Return _object
  67.         End Method
  68.        
  69.         Rem:doc
  70.         Returns the user-info object for the notification.
  71.         EndRem
  72.         Method UserInfo:Object()
  73.                 Return _userinfo
  74.         End Method
  75. End Type
  76.  
  77. Global NotificationTypeId:TTypeId = TTypeId.ForName("TNotification")
  78.  
  79. Public
  80.  
  81. Rem:doc
  82.         Notification center type.
  83. EndRem
  84. Type TNotificationCenter
  85.         Field _queue:TList {Restricted}
  86.         Field _observers:TList {Restricted}
  87.        
  88.         ?Threaded
  89.         Global _mainCenterLock:TMutex
  90.         Global _mainCenter:TNotificationCenter
  91.         Global _default:TThreadData
  92.        
  93.         Field _lock:TMutex {Restricted}
  94.        
  95.         ?Not Threaded
  96.         Global _default:TNotificationCenter
  97.         ?
  98.        
  99. '#region Instance methods
  100.        
  101.         Method New()
  102.                 ?Threaded
  103.                 _lock = TMutex.Create()
  104.                 ?
  105.                
  106.                 _queue = New TList
  107.                 _observers = New TList
  108.         End Method
  109.        
  110.         Method Delete()
  111.                 ProcessQueue()
  112.                 ?Threaded
  113.                 _lock.Close()
  114.                 ?
  115.         End Method
  116.        
  117.         Rem:doc
  118.                 Disposes of the notification center.  You probably don't need to call
  119.                 this for any reason, but it provides a means of potentially expediting
  120.                 the release of a notification center.
  121.         EndRem
  122.         Method Dispose()
  123.                 ProcessQueue()
  124.                 ?Threaded
  125.                
  126.                 If _default.GetValue() = Self Then
  127.                         _default.SetValue(Null)
  128.                 EndIf
  129.                
  130.                 If CurrentThread() = MainThread() Then
  131.                         _mainCenterLock.Lock()
  132.                         If _mainCenter = Self Then
  133.                                 _mainCenter = Null
  134.                         EndIf
  135.                         _mainCenterLock.Unlock()
  136.                 EndIf
  137.                
  138.                 ?Not Threaded
  139.                
  140.                 If _default = Self Then
  141.                         _default = Null
  142.                 EndIf
  143.                
  144.                 ?
  145.         End Method
  146.        
  147.         Rem:doc
  148.                 Makes the target object the default notification center for the
  149.                 current thread.
  150.         EndRem
  151.         Method MakeDefault()
  152.                 ?Threaded
  153.                 _default.SetValue(Self)
  154.                 ?Not Threaded
  155.                 _default = Self
  156.                 ?
  157.         End Method
  158.        
  159.         Rem:doc
  160.                 Posts a notification.  This will send the notification to all
  161.                 observers of the {param:notification}.
  162.                
  163.                 @param:notification The name of the notification.
  164.                 @param:obj The object that spawned the notification, if any.
  165.                 @param:userinfo Any information to be passed along with the
  166.                 notification.  E.g., a map containing information pertinent to the
  167.                 notification.
  168.         EndRem
  169.         Method PostNotificationWithName(notification:String, obj:Object=Null, userinfo:Object=Null)
  170.                 PostNotification(New TNotification.InitWithName(notification, obj, userinfo))
  171.         End Method
  172.        
  173.         Rem:doc
  174.                 Posts a notification.  This will send the notification to all
  175.                 observers of the {param:notification}.
  176.                 @param:notification The notification.
  177.         EndRem
  178.         Method PostNotification(notification:TNotification)
  179.                 ?Threaded
  180.                 _lock.Lock()
  181.                 ?
  182.                 Local observers:Object[] = _observers.ToArray()
  183.                 ?Threaded
  184.                 _lock.Unlock()
  185.                 ?
  186.                
  187.                 Local assObj:Object = notification.AssociatedObject()
  188.                 Local assName:String = notification.Name()
  189.                 Local invoker:TMethod = Null
  190.                 Local args:Object[1]
  191.                 args[0] = notification
  192.                 For Local o:TNotificationObserver = EachIn observers
  193.                         If (o.forObject <> Null And o.forObject <> assObj) Or (o.name <> Null And o.name <> assName) Then
  194.                                 Continue
  195.                         EndIf
  196.                        
  197.                         invoker = o.invocation
  198.                         If invoker Then
  199.                                 invoker.Invoke(o.observer,args)
  200.                         Else
  201.                                 o.observer.SendMessage(notification, Self)
  202.                         EndIf
  203.                 Next
  204.         End Method
  205.        
  206.         '#region Queueing
  207.        
  208.         Rem:doc
  209.                 Posts any notifications currently in the queue.
  210.         EndRem
  211.         Method ProcessQueue()
  212.                 ?Threaded
  213.                 _lock.Lock()
  214.                 ?
  215.                 Local notifications:Object[] = _queue.ToArray()
  216.                 _queue.Clear()
  217.                 ?Threaded
  218.                 _lock.Unlock()
  219.                 ?
  220.                
  221.                 For Local n:TNotification = EachIn notifications
  222.                         PostNotification(n)
  223.                 Next
  224.         End Method
  225.        
  226.         Rem:doc
  227.                 Queues a {param:notification} with the notification center.
  228.                
  229.                 @param:notification The name of the notification.
  230.                 @param:obj The object that spawned the notification, if any.
  231.                 @param:userinfo Any information to be passed along with the
  232.                 notification.  E.g., a map containing information pertinent to the
  233.                 notification.
  234.         EndRem
  235.         Method EnqueueNotificationWithName(notification:String, obj:Object=Null, userinfo:Object=Null)
  236.                 ?Threaded
  237.                 _lock.Lock()
  238.                 ?
  239.                 EnqueueNotification(New TNotification.InitWithName(notification, obj, userinfo))
  240.                 ?Threaded
  241.                 _lock.Unlock()
  242.                 ?
  243.         End Method
  244.        
  245.         Rem:doc
  246.                 Queues a {param:notification} with the notification center.
  247.                 @param:notification The notification.
  248.         EndRem
  249.         Method EnqueueNotification(notification:TNotification)
  250.                 ?Threaded
  251.                 _lock.Lock()
  252.                 ?
  253.                 _queue.AddLast(notification)
  254.                 ?Threaded
  255.                 _lock.Unlock()
  256.                 ?
  257.         End Method
  258.        
  259.         '#endregion
  260.        
  261.         '#region Observing
  262.        
  263.         Rem:doc
  264.                 Adds an {param:observer} for the specified {param:notification} and
  265.                 {param:object|forObject}.
  266.                
  267.                 @param:observer The observing object.
  268.                 @param:methodName The name of the method that will be invoked for any
  269.                 observed notifications.  If the method cannot be found, notifications
  270.                 are passed to Object.SendMessage as the message, and the notification
  271.                 center as the context object.
  272.                 @param:notification The name of the notification to observe.  If null
  273.                 or empty, the object will observe all notification.
  274.                 @param:forObject The object to observe notifications from.  If null,
  275.                 the observer will receive all notifications.
  276.                
  277.                 @note Due to the lack of weak references in BlitzMax, observers and
  278.                 any objects they are observing will remain in memory so long as they
  279.                 are observing/being observer.  To ensure you do not leak memory,
  280.                 remove the observer when disposing of objects or when the object no
  281.                 longer needs to observe notifications.
  282.         EndRem
  283.         Method AddObserver(observer:Object, methodName:String, notification:String=Null, forObject:Object=Null)
  284.                 Assert observer, "Null observer"
  285.                
  286.                 methodName = methodName.Trim()
  287.                 Assert methodName.Length, "Empty method name"
  288.                 Local o:TNotificationObserver = New TNotificationObserver
  289.                
  290.                 o.observer = observer
  291.                 o.forObject = forObject
  292.                 o.name = notification
  293.                
  294.                 o.invocation = TTypeId.ForObject(observer).FindMethod(methodName)
  295.                 If o.invocation Then
  296.                         ' ensure the method only accepts the notification
  297.                         Local args:TTypeId[] = o.invocation.ArgTypes()
  298.                         Assert args.Length=1 And args[0] = NotificationTypeId, ..
  299.                                 "Invalid parameters for method ~q"+methodName+"~q; must be "+methodName+"(n:TNotification)"
  300.                 EndIf
  301.                
  302.                 ?Threaded
  303.                 _lock.Lock()
  304.                 ?
  305.                 _observers.AddLast(o)
  306.                 ?Threaded
  307.                 _lock.Unlock()
  308.                 ?
  309.         End Method
  310.        
  311.         Rem:doc
  312.                 Removes an {param:observer} for the specified {param:notification} and
  313.                 {param:object|forObject}.
  314.                
  315.                 @param:observer The observer.
  316.                 @param:notification The notification being observed.  If null, will
  317.                 match any instance of the observer.
  318.                 @param:forObject The object being observed.  If null, will match any
  319.                 instance of the observer.
  320.         EndRem
  321.         Method RemoveObserver(observer:Object, notification:String=Null, forObject:Object=Null)
  322.                 If observer = Null Then Return
  323.                 ?Threaded
  324.                 _lock.Lock()
  325.                 ?
  326.                 Local observers:Object[] = _observers.ToArray()
  327.                 If notification And forObject Then
  328.                         For Local o:TNotificationObserver = EachIn observers
  329.                                 If o.observer <> observer Then
  330.                                         Continue
  331.                                 EndIf
  332.                                
  333.                                 If o.name = notification And o.forObject = forObject Then
  334.                                         _observers.Remove(o)
  335.                                 EndIf
  336.                         Next
  337.                 ElseIf notification Then
  338.                         For Local o:TNotificationObserver = EachIn observers
  339.                                 If o.observer <> observer Then
  340.                                         Continue
  341.                                 EndIf
  342.                                
  343.                                 If o.name = notification Then
  344.                                         _observers.Remove(o)
  345.                                 EndIf
  346.                         Next
  347.                 ElseIf forObject Then
  348.                         For Local o:TNotificationObserver = EachIn observers
  349.                                 If o.observer <> observer Then
  350.                                         Continue
  351.                                 EndIf
  352.                                
  353.                                 If o.forObject = forObject Then
  354.                                         _observers.Remove(o)
  355.                                 EndIf
  356.                         Next
  357.                 Else
  358.                         For Local o:TNotificationObserver = EachIn observers
  359.                                 If o.observer = observer Then
  360.                                         _observers.Remove(o)
  361.                                 EndIf
  362.                         Next
  363.                 EndIf
  364.                 ?Threaded
  365.                 _lock.Unlock()
  366.                 ?
  367.         End Method
  368.        
  369.         '#endregion
  370.        
  371. '#endregion
  372.        
  373. '#region Class methods
  374.        
  375.         Rem:doc
  376.                 Internal, do not call.
  377.                
  378.                 @note There should not be any side-effects from calling this method,
  379.                 but it is not intended to be called more than once, and this module
  380.                 calls it.
  381.         EndRem
  382.         Function Initialize()
  383.                 ?Threaded
  384.                 If Not _mainCenterLock Then
  385.                         _mainCenterLock = TMutex.Create()
  386.                 EndIf
  387.                
  388.                 If Not _default Then
  389.                         _default = TThreadData.Create()
  390.                 EndIf
  391.                 ?
  392.                 DefaultCenter()
  393.         End Function
  394.        
  395.         Rem:doc
  396.                 Posts queued notifications for the default center of the calling
  397.                 thread.
  398.         EndRem
  399.         Function Process()
  400.                 Local center:TNotificationCenter = DefaultCenter()
  401.                 center.ProcessQueue()
  402.         End Function
  403.        
  404.         Rem:doc
  405.                 Gets the default notification center for the calling thread.
  406.                 @return The default notification center for the calling thread.
  407.         EndRem
  408.         Function DefaultCenter:TNotificationCenter()
  409.                 ?Threaded
  410.                         Local center:TNotificationCenter = TNotificationCenter(_default.GetValue())
  411.                        
  412.                         If center = Null Then
  413.                                 center = New TNotificationCenter
  414.                                 _default.SetValue(center)
  415.                                
  416.                                 If CurrentThread() = MainThread() Then
  417.                                         _mainCenterLock.Lock()
  418.                                         _mainCenter = center
  419.                                         _mainCenterLock.Unlock()
  420.                                 EndIf
  421.                         EndIf
  422.                        
  423.                         Return center
  424.                 ?Not Threaded
  425.                         If _default = Null Then
  426.                                 _default = New TNotificationCenter
  427.                         EndIf
  428.                         Return _default
  429.                 ?
  430.         End Function
  431.        
  432.         Rem:doc
  433.                 Gets the default notification center for the main thread.
  434.                 @return The default notification center for the main thread.
  435.         EndRem
  436.         Function DefaultCenterForMainThread:TNotificationCenter()
  437.                 ?Threaded
  438.                 Return TNotificationCenter._mainCenter
  439.                 ?Not Threaded
  440.                 Return TNotificationCenter.DefaultCenter()
  441.                 ?
  442.         End Function
  443.        
  444. '#endregion
  445. End Type
  446. TNotificationCenter.Initialize()


Comments :


_PJ_(Posted 1+ years ago)

 BMax code


N(Posted 1+ years ago)

 Good observation.  Would you like a merit badge?


_PJ_(Posted 1+ years ago)

 No, but I would prefer to find BB code when I look under BB code. There's a separate place for the BMax code.


N(Posted 1+ years ago)

 What are you, some kind of code racist person?  Against intercode relations?Seriously though, complain about something that can a) be fixed and b) matters.


RifRaf(Posted 1+ years ago)

 it matters because the archives should be organized properly, over time things could get hard to sort. So posters should do their part and label them appropriatly as bb or bmx.      Just pay attention next time, and hopefully if things get too confusing by mislabelling the mods will start correcting it.  No need to get rude with someone who was trying to remind you


Ked(Posted 1+ years ago)

 <div class="quote"> a) be fixed </div>+1 for feature


_PJ_(Posted 1+ years ago)

 Sorry Noel,  my original "BlitzMax Code" comment may have been rather terse, but to be honest, it was not intended as an "attack" merely a useful reminder for any others like myself that had been browsing the archives where this code is listed under .bbI appreciate there's no point crying ovber spilt milk, and sure, accidents happen. Though a little consideration when posting would hopefully prevent this from happening in the future. Perhaps BMax AND B3D users as opposed to those who own only one of the products don't appreciate so much how annoying it can be when you find code in the archives only to realise it's unuseable.


 

SimplePortal 2.3.6 © 2008-2014, SimplePortal