{"id":102,"date":"2008-08-07T21:52:03","date_gmt":"2008-08-07T09:52:03","guid":{"rendered":"https:\/\/www.deltics.co.nz\/blog\/?p=102"},"modified":"2009-08-07T14:15:37","modified_gmt":"2009-08-07T02:15:37","slug":"multicast-events-part-2","status":"publish","type":"post","link":"https:\/\/www.deltics.co.nz\/blog\/posts\/102\/","title":{"rendered":"Multicast Events &#8211; Part 2"},"content":{"rendered":"<span class=\"rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">[Estimated Reading Time: <\/span> <span class=\"rt-time\">8<\/span> <span class=\"rt-label rt-postfix\">minutes]<\/span><\/span><p>Having covered some of the basic use of multicast events, in this second post I shall start to build the implementation.\u00a0 In this first iteration we will provide the basics of a multicast event &#8211; managing and calling multiple handlers and the ability to enable and disable an event.<\/p>\n<p>The test project used in the previous video demonstration may also now be <a href=\"https:\/\/www.deltics.co.nz\/download\/multicast1.zip\">downloaded<\/a> for you to experiment with if you wish.<\/p>\n<p><!--more--><\/p>\n<h3>First An Apology<\/h3>\n<p>Rather than throwing fully formed code at you and then hoping you can follow me as I attempt to walk you through it, I am instead recreating the process I went through in evolving the implementation, building from the basics and only adding the <em>really<\/em> clever stuff later.<\/p>\n<p>So apologies if you&#8217;d rather just get to grips with the finished implementation &#8211; you&#8217;ll just have to wait until the series is done.\u00a0 It won&#8217;t actually take that long &#8211; one more post in the series I think &#8211; but I hope you&#8217;ll find the wait worthwhile.<\/p>\n<p>\ud83d\ude42<\/p>\n<h3>From Little Acorns<\/h3>\n<p>As mentioned in <a href=\"https:\/\/www.deltics.co.nz\/blog\/?p=86\">part 1<\/a>, the implementation builds on a base class that provides the foundation for all multicast events.\u00a0 Rather unimaginatively I called it <strong>TMultiCastEvent<\/strong>:<\/p>\n<pre class=\"delphi\">    TMultiCastEvent = class\r\n    private\r\n      fDisableCount: Integer;\r\n      fMethods: TList;\r\n      function get_Count: Integer;\r\n      function get_Enabled: Boolean;\r\n      function get_Method(const aIndex: Integer): TMethod;\r\n      procedure set_Enabled(const aValue: Boolean);\r\n    protected\r\n      procedure Call(const aMethod: TMethod); virtual; abstract;\r\n      procedure Add(const aMethod: TMethod);\r\n      procedure Remove(const aMethod: TMethod);\r\n      property Method[const aIndex: Integer]: TMethod read get_Method;\r\n    public\r\n      constructor Create; virtual;\r\n      destructor Destroy; override;\r\n      procedure DoEvent;\r\n      property Count: Integer read get_Count;\r\n      property Enabled: Boolean read get_Enabled write set_Enabled;\r\n    end;<\/pre>\n<p>I shall not go into every tiny detail of the implementation &#8211; you can inspect the code yourself in the files accompanying this post &#8211; but will concentrate on the (hopefully) more interesting aspects.<\/p>\n<p>For instance, something may strike you as odd about the <strong>Add<\/strong> and <strong>Remove<\/strong> methods &#8211; they have only <em>protected<\/em> visibility and so will not be visible to us when we create events!\u00a0 How are we going to add and remove handlers?<\/p>\n<p>We&#8217;ll see that this won&#8217;t be a problem, and there is a very good reason for doing it this way.<\/p>\n<h3>TMethod vs A &#8220;Real&#8221; Method<\/h3>\n<p>The <strong>TMethod<\/strong> type, representing the code and data pointers of a method reference, is not particularly usable directly.\u00a0 I think in part this is because of the special treatment it receives from the compiler and the runtime, although I don&#8217;t know this for sure.\u00a0 Consider for example that <em>Assigned()<\/em> and <em>NIL<\/em> work equally well with event handler references and object references, yet event handlers consist of <em>two<\/em> pointers, where object references consist of only <em>one<\/em>.\u00a0 Magic, eh?<\/p>\n<p>A particular problem is that a <strong>TMethod<\/strong> record can refer to any type of object method, regardless of that method&#8217;s parameters or return type.\u00a0 Not only does this make working directly with <strong>TMethod<\/strong> difficult, it makes it downright dangerous!<\/p>\n<p>So before delving any deeper into <strong>TMultiCastEvent<\/strong>, let&#8217;s look briefly at the <strong>TMultiCastNotify<\/strong> class that is used in the <a href=\"https:\/\/www.deltics.co.nz\/blog\/?p=112\">video demo I previously posted<\/a>, to see how I protect myself from the non-specific nature of <strong>TMethod<\/strong>:<\/p>\n<pre class=\"delphi\">    TMultiCastNotify = class(TMultiCastEvent)\r\n    private\r\n      fSender: TObject;\r\n    protected\r\n      property Sender: TObject read fSender;\r\n      procedure Call(const aMethod: TMethod); override;\r\n    public\r\n      constructor Create(const aSender: TObject); reintroduce; virtual;\r\n      procedure Add(const aHandler: TNotifyEvent);\r\n      procedure Remove(const aHandler: TNotifyEvent);\r\n    end;<\/pre>\n<p>This class extends <strong>TMultiCastEvent<\/strong> for the purpose of creating a multicast version of a <strong>TNotifyEvent<\/strong>.<\/p>\n<p>You may have realised by now that you will need to derive a specialised class for each event type for which you need a multicast implementation.\u00a0 This may sound arduous, but it all depends how many such events you actually need.\u00a0 In practice I have found that <strong>TMultiCastNotify<\/strong> covers almost all of my multicast needs, and you&#8217;ll be getting that for free when this series is done!<\/p>\n<p>In <strong>TMultiCastNotify<\/strong> we provide <em>public<\/em> <strong>Add<\/strong> and <strong>Remove<\/strong> methods with type-safe parameters accepting <strong>TNotifyEvent<\/strong> handlers, which simply delegate to the inherited implementation after casting the parameters to the required <strong>TMethod<\/strong> type:<\/p>\n<pre class=\"delphi\">  procedure TMultiCastNotify.Add(const aHandler: TNotifyEvent);\r\n  begin\r\n    inherited Add(TMethod(aHandler));\r\n  end;\r\n\r\n  procedure TMultiCastNotify.Remove(const aHandler: TNotifyEvent);\r\n  begin\r\n    inherited Remove(TMethod(aHandler));\r\n  end;<\/pre>\n<p><strong>NOTE:<\/strong> If the generics support in Tiburon will allow event types to be used, I am happy to admit that I may actually have found a use for them outside of simple collections!\u00a0 Even I can see the appeal of being able to write:<\/p>\n<pre class=\"delphi\">TMultiCastNotify = class(TMultiCast&lt;TNotifyEvent&gt;)<\/pre>\n<p>If anyone from CodeGear is reading this, it would be useful to learn whether this is something that is going to be possible.<\/p>\n<p>\ud83d\ude09<\/p>\n<p>For now we will have to make do without generics &#8211; which does at least have the advantage of allowing us to use this code in older versions of Delphi!<\/p>\n<h3>Managing The Handlers<\/h3>\n<p>As mentioned, a <strong>TMethod<\/strong> consists of two pointers, so storing these in a <strong>TList <\/strong>presents a difficulty.\u00a0 To get around this, I chose simply to copy each <strong>TMethod<\/strong> before adding it to the list.\u00a0 Although Delphi defines a <strong>TMethod<\/strong> type, it doesn&#8217;t (as of Delphi 7 anyway) define a corresponding <strong>PMethod<\/strong> pointer type, so I declare this for myself allowing me to use <em>New()<\/em> and <em>Dispose()<\/em> to manage the copies of <strong>TMethod<\/strong> records that I need, and to store <span style=\"text-decoration: underline;\">pointers<\/span> to those copies in my <strong>fMethods<\/strong> list.<\/p>\n<p>I decided for my implementation that it should not be possible to add the same handler to one event multiple times.\u00a0 That is, an event may have many handlers, and any one handler might respond to more than one event, but it cannot respond more than once to any one event.<\/p>\n<p>So, before adding a method to the list I first check to ensure it is not already present by comparing the <strong>Data<\/strong> <span style=\"text-decoration: underline;\">and<\/span> <strong>Code<\/strong> pointers to those methods already in the list:<\/p>\n<pre class=\"delphi\">procedure TMultiCastEvent.Add(const aMethod: TMethod);\r\nvar\r\n  i: Integer;\r\n  method: PMethod;\r\nbegin\r\n  if NOT Assigned(self) then\r\n    EXIT;\r\n\r\n  \/\/ Ensure that the specified method is not already attached\r\n  for i := 0 to Pred(fMethods.Count) do\r\n  begin\r\n    method := fMethods[i];\r\n\r\n    if (aMethod.Code = method.Code) and (aMethod.Data = method.Data) then\r\n      EXIT;\r\n  end;\r\n\r\n  \/\/ Not already attached - create a new TMethod reference and copy the\r\n  \/\/\u00a0 details from the specific method, then add to our list of methods\r\n  method := New(PMethod);\r\n  method.Code := aMethod.Code;\r\n  method.Data := aMethod.Data;\r\n  fMethods.Add(method);\r\nend;<\/pre>\n<p>Notice that the method checks for a <strong>NIL<\/strong> self &#8211; this creates a NIL-safe semantic for multicast events.  Attempting to add a handler to a NIL multicast event is a null operation, not an error.  There is of course a corresponding <em>Remove()<\/em> method, also NIL-safe, which removes an identified method from the list, remembering that the entry in the list is a copy that has to be disposed once removed:<\/p>\n<pre class=\"delphi\">  procedure TMultiCastEvent.Remove(const aMethod: TMethod);\r\n  var\r\n    i: Integer;\r\n    method: PMethod;\r\n  begin\r\n    if NOT Assigned(self) then\r\n      EXIT;\r\n\r\n    for i := 0 to Pred(fMethods.Count) do\r\n    begin\r\n      method := fMethods[i];\r\n\r\n      if (aMethod.Code = method.Code) and (aMethod.Data = method.Data) then\r\n      begin\r\n        Dispose(method);\r\n        fMethods.Delete(i);\r\n\r\n        \/\/ Only one reference to any method can be attached to any one event, so\r\n        \/\/  once we have found and removed the method there is no need to check the\r\n        \/\/  remaining entries.\r\n        BREAK;\r\n      end;\r\n    end;\r\n  end;<\/pre>\n<p>These take care of the basics of adding and removing the handler methods for a multicast event, with a bit of type-safety housekeeping from an appropriate derived class.<\/p>\n<h3>Enabling Disabling<\/h3>\n<p>The implementation of the <strong>Enabled<\/strong> property should be fairly obvious from the presence of the <strong>fDisableCount<\/strong> property, and the fact that it is an Integer.<\/p>\n<p>Setting <strong>Enabled<\/strong> to <strong>FALSE<\/strong> <em>inc<\/em>rements this count, and setting <strong>Enabled<\/strong> to <strong>TRUE<\/strong> <em>dec<\/em>rements it.\u00a0 An event is enabled if the disabled count is zero.<\/p>\n<p>Fairly standard stuff.<\/p>\n<h3>Call Me<\/h3>\n<p>So much for adding and removing handlers and disabling an event.\u00a0 The real interest in how event handlers are invoked by an event.\u00a0 First let&#8217;s look at how a normal, unicast event might usually be coded, using an <strong>OnClick: TNotifyEvent<\/strong> as an example:<\/p>\n<pre class=\"delphi\">if Assigned(OnClick) then\r\n  OnClick(self);<\/pre>\n<p>This should be instantly familiar.\u00a0 Of course, a multicast event has a list of such handlers but doesn&#8217;t need to worry about whether they are <em>Assigned()<\/em> or not, so to that extent the job is a little easier &#8211; the event simply needs to iterate over the list of handlers and call each one in turn.\u00a0 Easy.<\/p>\n<p>With just one problem.<\/p>\n<p>The list contains (pointers to)<strong> TMethod<\/strong> records, with no clues as to what parameters are needed to be passed to those methods.\u00a0 So, unfortunately, a little work is required in our event specific derived classes.<\/p>\n<p>The base class is able to do one thing for us (under certain circumstances) &#8211; it can iterate over the list of handlers and pass each one to that virtual <em>Call()<\/em> method.\u00a0 <em>Call()<\/em> is <em>abstract<\/em> (the base class simply doesn&#8217;t know how to call the methods) &#8211; our derived classes must implement <em>Call()<\/em> and invoke the specified method in the appropriate fashion.<\/p>\n<pre class=\"delphi\">  procedure TMultiCastEvent.DoEvent;\r\n  var\r\n    i: Integer;\r\n  begin\r\n    if NOT Assigned(self) or (NOT Enabled) then\r\n      EXIT;\r\n\r\n    for i := 0 to Pred(Count) do\r\n      Call(Method[i]);\r\n  end;<\/pre>\n<p>Again, notice that this method has a NIL-safe semantic.  Calling <em>DoEvent()<\/em> using a <strong>NIL<\/strong> event reference is a null operation.<\/p>\n<p>In <strong>TMultiCastNotify<\/strong>, the implementation of <em>Call()<\/em> is trivial:<\/p>\n<pre class=\"delphi\">  procedure TMultiCastNotify.Call(const aMethod: TMethod);\r\n  begin\r\n    TNotifyEvent(aMethod)(Sender);\r\n  end;<\/pre>\n<p>We cast the method reference back to the appropriate event signature and call it with the appropriate parameters.  In this case the only parameter is the Sender, specified when the event was created, so the <em>Call()<\/em> method itself has all the information it needs to invoke the handlers.<\/p>\n<h3>Limitations Of The Base Implementation<\/h3>\n<p>As I said, this mechanism is only useful under certain circumstances &#8211; it should be apparent that the basic <em>DoEvent()<\/em> implementation is only of use for events where the parameters passed to the handlers are all specified in the constructor of an event (or set via properties prior to firing) so that no further parameters need be passed to the event when it actually fires.<\/p>\n<p>If the parameters may change from one firing of the event to the next then we cannot use the inherited <em>DoEvent()<\/em> and an overriden <em>Call()<\/em> method, we must reintroduce our own <em>DoEvent()<\/em> and invoke the methods directly.<\/p>\n<p>A hypothetical example of a some event with <strong>Sender<\/strong>, <strong>X<\/strong> and <strong>Y<\/strong> parameters might look something like this:<\/p>\n<pre class=\"delphi\">procedure TMultiCastCoordEvent.DoEvent(const aX, aY: Integer);\r\nvar\r\n  i: Integer;\r\nbegin\r\n  if NOT Assigned(self) or NOT Enabled then\r\n    EXIT;\r\n\r\n  for i := 0 to Pred(Count) do\r\n    TMouseEvent(Methods[i])(Sender, aX, aY);\r\nend;<\/pre>\n<p>Notice that <strong>Sender<\/strong> is <span style=\"text-decoration: underline;\">not<\/span> passed in to <em>DoEvent()<\/em> &#8211; we may assume it was passed via the constructor of the event, just as for <strong>TMultiCastNotify<\/strong> &#8211; but the <strong>X<\/strong> and <strong>Y<\/strong> parameters have to be specified at <em>DoEvent()<\/em> time as they may vary each time the event fires.<\/p>\n<p>It&#8217;s a shame that we have to do quite so much work &#8211; enforcing the NIL-safe semantics <span style=\"text-decoration: underline;\">and<\/span> respecting <strong>Enabled<\/strong> indicator as well as iterating over and calling each method.  But we only have to do this once for each event signature for which a multicast class need be derived.<\/p>\n<p>I say &#8220;have to&#8221; but actually there is an alternative.\u00a0 We could require event parameters to be set-up via properties before firing the event using the inherited <em>DoEvent()<\/em> implementation:<\/p>\n<pre class=\"delphi\">    procedure TMultiCastCoordEvent.SetParams(const aX, aY: Integer);\r\n    begin\r\n      fX := aX;\r\n      fY := aY;\r\n    end;\r\n\r\n    procedure TMultiCastCoordEvent.Call(const aMethod: TMethod); {override}\r\n    begin\r\n      TCoordEvent(aMethod)(Sender, fX, fY);\r\n    end;\r\n\r\n  \/\/ To fire  - set the params and do the event\r\n  fMouseEvent.SetParams(X, Y);\r\n  fMouseEvent.DoEvent;<\/pre>\n<p>If you implement your own multicast events based on <strong>TMultiCastEvent<\/strong> you might prefer this approach.<\/p>\n<p>To be perfectly candid, I only just spotted this approach &#8211; one of the benefits of explaining something to someone else is that you often see things you didn&#8217;t see before!\u00a0 However, although this alternative approach makes implementing a new multicast event easier (a one time effort) it does come at the expense of slightly more cumbersome usage (a repeated effort).<\/p>\n<p>Your choice.<\/p>\n<p>\ud83d\ude42<\/p>\n<h3>A Little Effort For Greater Rewards<\/h3>\n<p>In any event, whilst an (avoidable) annoyance in many cases, providing a &#8220;complete&#8221; implementation of <em>DoEvent()<\/em> for an event class is an opportunity in others, allowing us to introduce different behaviours and capabilities in <span style=\"text-decoration: underline;\">very<\/span> specialised event types if required.<\/p>\n<p>For example I have two highly specialised events for very specific purposes in certain projects:<\/p>\n<p style=\"padding-left: 30px;\"><strong>1. A Cancellable Event<\/strong><\/p>\n<p style=\"padding-left: 30px;\">The handler signature for this event accepts a var boolean param.\u00a0 If any handler sets this parameter <strong>TRUE<\/strong>, the <em>DoEvent()<\/em> implementation halts iteration of the handlers, cancelling that firing of the event.<\/p>\n<p style=\"padding-left: 30px;\"><strong>2. Guaranteed First (and Last) Invocation<\/strong><\/p>\n<p style=\"padding-left: 30px;\">I also have an event class that supports &#8220;Initial&#8221; and &#8220;Final&#8221; handlers &#8211; that is, individual unicast handlers that are guaranteed to be invoked before or after &#8211; respectively &#8211; any handlers in the multicast handler list.<\/p>\n<p style=\"padding-left: 30px;\">Order of execution for event handlers <span style=\"text-decoration: underline;\">shouldn&#8217;t<\/span> normally matter.\u00a0 In fact, a handler should not ordinarily even need to know whether there even <em>are<\/em> any other event handlers, let alone what order they are being invoked in.\u00a0 But as I say, I had a <span style=\"text-decoration: underline;\">very<\/span> specialised need.<\/p>\n<h3>To Recap<\/h3>\n<p>The implementation at this stage comprised:<\/p>\n<p style=\"padding-left: 30px;\"><strong><em>&#8211; Support for multicast events<\/em><\/strong><\/p>\n<p style=\"padding-left: 60px;\">Adding and removing handlers, Enabling, Disabling and firing events<\/p>\n<p style=\"padding-left: 30px;\"><strong><em>&#8211; NIL-safe semantics<\/em><\/strong><\/p>\n<p style=\"padding-left: 60px;\">Adding\/Removing handlers to a NIL event &#8211; null operation<\/p>\n<p style=\"padding-left: 60px;\">Firing a NIL event &#8211; null operation<\/p>\n<p>It did however contain a critical flaw.<\/p>\n<p>If a handler were added to an event, and the object that implemented that handler was destroyed without having removed that handler, then the next time the event fired it would call into an invalid handler.\u00a0 At best an access violation would occur.\u00a0 At worst some unexpected and undefined behaviour would result.<\/p>\n<p>There is a solution to that problem, and that is what I shall explore in my next post in this series.<\/p>\n<p>In the meantime you may <a href=\"https:\/\/www.deltics.co.nz\/download\/multicast1.zip\">download the demo project and basic multicast event implementation<\/a> as it stood at this stage in it&#8217;s development, to tinker and explore with.<\/p>\n","protected":false},"excerpt":{"rendered":"<p><span class=\"rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">[Estimated Reading Time: <\/span> <span class=\"rt-time\">8<\/span> <span class=\"rt-label rt-postfix\">minutes]<\/span><\/span> Having covered some of the basic use of multicast events, in this second post I shall start to build the implementation.\u00a0 In this first iteration we will provide the basics of a multicast event &#8211; managing and calling multiple handlers and the ability to enable and disable an event. The test project used in the [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"jetpack_publicize_message":"","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":[]},"categories":[4],"tags":[292,28,21,25,26,27],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p1TKYv-1E","jetpack_sharing_enabled":true,"jetpack-related-posts":[{"id":86,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/86\/","url_meta":{"origin":102,"position":0},"title":"Multicast Events &#8211; Part 1","date":"05 Aug 2008","format":false,"excerpt":"I remember one of the things that got me excited when I first read about the as-then new fangled C# and .NET stuff coming out of Microsoft was the idea that a single event could have multiple handlers. Cutting a long and irrelevant story short, my interest in .NET waned,\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":160,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/160\/","url_meta":{"origin":102,"position":1},"title":"MultiCast Events &#8211; Conclusion","date":"11 Aug 2008","format":false,"excerpt":"Drawing this subject to a close (finally!), here's the concluding post I promised, including the fully documented and finished implementation that has been serving me well for almost 2 years.\u00a0 The finished implementation incorporates a number of refinements to the core framework, and those are what we shall briefly look\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":112,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/112\/","url_meta":{"origin":102,"position":2},"title":"Multicast Events &#8211; Video Demo 1","date":"07 Aug 2008","format":false,"excerpt":"My second post on multicast events is now up, and here's a video showing the basics. It was also an excuse to get to grips with the video capturing software - CamStudio - (and technique!), which proved to be a frustrating exercise to say the least, but I am quite\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":137,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/137\/","url_meta":{"origin":102,"position":3},"title":"Multicast Events &#8211; Part 3","date":"09 Aug 2008","format":false,"excerpt":"So far we've seen a multicast event implementation in (fairly limited) action, and dissected the core of it's implementation, which was a fairly dry affair. I also demonstrated a flaw in the initial implementation - a susceptibility to objects adding handlers to events but not removing them when being destroyed.\u00a0\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":147,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/147\/","url_meta":{"origin":102,"position":4},"title":"MultiCast Events &#8211; Video #2","date":"09 Aug 2008","format":false,"excerpt":"At the end of my last post I described a problem that arises if an object adds handlers to an event and is then destroyed without having removed those handlers.\u00a0 This video demonstrates that problem and also shows a solution provided by the multicast events framework itself. The details of\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":180,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/180\/","url_meta":{"origin":102,"position":5},"title":"Latest Tibur\u00f3n Preview","date":"14 Aug 2008","format":false,"excerpt":"I was a little disappointed that the preview webinar this morning was little more than a re-run of the same content from a little over a week ago, albeit with some downloadable PowerPoint slides this time. It was at least an opportunity for some more Q&A and a couple of\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]}],"_links":{"self":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/102"}],"collection":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/comments?post=102"}],"version-history":[{"count":23,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/102\/revisions"}],"predecessor-version":[{"id":467,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/102\/revisions\/467"}],"wp:attachment":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/media?parent=102"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/categories?post=102"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/tags?post=102"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}