/interpreter/tags/reactive-pattern-matching/src/edu/vub/at/signals/natives/NATSignal.java
Java | 260 lines | 131 code | 50 blank | 79 comment | 14 complexity | d34b339d73c347e1137f2726f868e318 MD5 | raw file
1/** 2 * AmbientTalk/2 Project 3 * NATSignal.java created on Apr 14, 2008 4 * (c) Programming Technology Lab, 2006 - 2007 5 * Authors: Tom Van Cutsem & Stijn Mostinckx 6 * 7 * Permission is hereby granted, free of charge, to any person 8 * obtaining a copy of this software and associated documentation 9 * files (the "Software"), to deal in the Software without 10 * restriction, including without limitation the rights to use, 11 * copy, modify, merge, publish, distribute, sublicense, and/or 12 * sell copies of the Software, and to permit persons to whom the 13 * Software is furnished to do so, subject to the following 14 * conditions: 15 * 16 * The above copyright notice and this permission notice shall be 17 * included in all copies or substantial portions of the Software. 18 * 19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 21 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 23 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 24 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 25 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 26 * OTHER DEALINGS IN THE SOFTWARE. 27 */ 28package edu.vub.at.signals.natives; 29 30import java.lang.reflect.Method; 31import java.util.HashSet; 32import java.util.Iterator; 33 34import edu.vub.at.actors.natives.ELActor; 35import edu.vub.at.eval.Evaluator; 36import edu.vub.at.exceptions.InterpreterException; 37import edu.vub.at.exceptions.XIllegalOperation; 38import edu.vub.at.objects.ATMessage; 39import edu.vub.at.objects.ATNil; 40import edu.vub.at.objects.ATNumber; 41import edu.vub.at.objects.ATObject; 42import edu.vub.at.objects.ATTable; 43import edu.vub.at.objects.natives.NATMethodInvocation; 44import edu.vub.at.objects.natives.NATNumber; 45import edu.vub.at.objects.natives.NATTable; 46import edu.vub.at.objects.natives.NativeATObject; 47import edu.vub.at.signals.ATSignal; 48import edu.vub.at.signals.SignalListener; 49import edu.vub.at.signals.eval.DataflowEngine; 50import edu.vub.at.signals.eval.EncapsulatingSignal; 51import edu.vub.at.util.logging.Logging; 52 53/** 54 * NATSignal is the abstract root class of all AmbientTalk signals. It implements the complete 55 * SignalListener interface and provides the required support to ensure that events originating 56 * from a (primitive) event source outside of AmbientTalk's event loop are properly translated 57 * into asynchronous messages. 58 * 59 * Additionally, the signal keeps track of the context in which it was created and ensures that 60 * if it was created in a reactive activation, it is properly unhooked before the activation is 61 * triggered anew. 62 * 63 * @author smostinc 64 */ 65public abstract class NATSignal extends NativeATObject implements ATSignal, SignalListener { 66 67 private int itsDependencyHeight; 68 private boolean hasBeenUnhooked; 69 private ELActor itsOwner; 70 71 protected HashSet itsDependents = new HashSet(); 72 protected final ATObject itsConsumer; 73 74 protected NATSignal(int initialDependencyHeight) { 75 this(initialDependencyHeight, null); 76 } 77 78 protected NATSignal(int initialDependencyHeight, ATObject theConsumer) { 79 // 1) Set the object that will consume all incoming signal updates 80 81 itsConsumer = theConsumer; 82 83 // 2) If this listener is created in the context of a reactive activation, the height 84 // of the encapsulating activation should be taken into account, when determining the 85 // stratum in which this signal listener should be allocated. 86 87 DataflowEngine theEngine = Evaluator.getDataflowEngine(); 88 EncapsulatingSignal theEncapsulator = theEngine.getEncapsulatingSignal(); 89 90 int theEncapsulatorsDependencyHeight = theEncapsulator!=null ? theEncapsulator.dependencyHeight() : 0; 91 92 itsDependencyHeight = Math.max(initialDependencyHeight, theEncapsulatorsDependencyHeight); 93 94 // 3) If this listener is created in the context of a reactive activation, updates to 95 // that activation imply that this listener becomes invalid and should be unhooked. 96 97 hasBeenUnhooked = false; 98 99 if(theEncapsulator != null) theEncapsulator.registerDependency(this); 100 101 // 4) To properly synchronize signal updates, external event sources should notify us 102 // of updates using asynchronous message sends. To distinguish between internal and 103 // and external event sources, we keep track of the actor that created the listener. 104 // Updates originating from another process can then be considered external. 105 106 itsOwner = ELActor.currentActor(); 107 } 108 109 public int dependencyHeight() { 110 return itsDependencyHeight; 111 } 112 113 public ATNumber meta_dependencyHeight() { 114 return NATNumber.atValue(itsDependencyHeight); 115 } 116 117 public void updateDependencyHeight(int newDependencyHeight) { 118 Logging.ReactiveProgramming_LOG.debug("Updating stratum of signal: " + this + " from: " + dependencyHeight() + " to: " + newDependencyHeight); 119 // Conceptually, updating the height could be as simple as this : 120 // itsDependencyHeight = Math.max(itsDependencyHeight, newDependencyHeight); 121 122 // However, it is important to ensure that if this listener has been enqueued for 123 // recomputation, that it occurs in the correct stratum to avoid update glitches. 124 if(newDependencyHeight > itsDependencyHeight) { 125 itsDependencyHeight = newDependencyHeight; 126 127 DataflowEngine theEngine = Evaluator.getDataflowEngine(); 128 129 // This ensures that the updated dependency height is reflected in the queue 130 // that is kept by the dataflow engine. 131 theEngine.reprioritize(this); 132 133 // Notify all dependent signals 134 for(Iterator dependentComputationIt = itsDependents.iterator(); 135 dependentComputationIt.hasNext();) { 136 SignalListener currentDependentComputation = (SignalListener) dependentComputationIt.next(); 137 138 // The new height of all dependent computations should at least be our own 139 // dependency height plus one. 140 currentDependentComputation.updateDependencyHeight(itsDependencyHeight + 1); 141 } 142 } 143 } 144 145 146 // Dependency Management 147 148 public ATNil meta_addDependent(SignalListener dependent) throws InterpreterException { 149 if(dependent.dependencyHeight() > this.dependencyHeight()) { 150 itsDependents.add(dependent); 151 } else { 152 throw new XIllegalOperation("Attempted to add a dependent signal with a lower dependency height than my own"); 153 } 154 155 return Evaluator.getNil(); 156 } 157 158 public ATNil meta_removeDependent(SignalListener dependent) throws InterpreterException { 159 itsDependents.remove(dependent); 160 161 return Evaluator.getNil(); 162 } 163 164 165 public void unhook() { 166 hasBeenUnhooked = true; 167 168 itsDependents.clear(); 169 } 170 171 // Update Handling 172 173 /** 174 * This method is declared final, since subclasses should override the protected method 175 * inner_scheduleUpdate instead of overriding this method directly. 176 */ 177 public final void scheduleUpdate(ATSignal theParent, ATMessage theUpdate) { 178 if( !shouldSynchronize(theParent, theUpdate)) 179 inner_scheduleUpdate(theParent, theUpdate); 180 } 181 182 protected void inner_scheduleUpdate(ATSignal theParent, ATMessage theUpdate) { 183 DataflowEngine theEngine = Evaluator.getDataflowEngine(); 184 185 theEngine.schedulePendingUpdate(theParent, this, theUpdate); 186 } 187 188 /** 189 * This method is declared final, since subclasses should override the protected method 190 * inner_performUpdate instead of overriding this method directly. 191 */ 192 public final boolean performUpdate(ATSignal theParent, ATMessage theUpdate) { 193 if(!hasBeenUnhooked) 194 inner_performUpdate(theParent, theUpdate); 195 196 return !hasBeenUnhooked; 197 } 198 199 protected void inner_performUpdate(ATSignal theParent, ATMessage theUpdate) { 200 try { 201 if(itsConsumer == null) { 202 propagateNewMessage(theUpdate); 203 } else { 204 theUpdate.base_sendTo(itsConsumer, theParent); 205 } 206 } catch (InterpreterException e) { 207 // Could not send the message succesfully 208 e.printStackTrace(); 209 } 210 }; 211 212 // Auxiliary Methods 213 214 /** 215 * This method tests whether the signal update, that has to be scheduled, originates from 216 * another process. If the update is external, this method will schedule an asynchronous 217 * message in the owning actors event loop. 218 */ 219 private boolean shouldSynchronize(ATSignal theParent, ATMessage theUpdate) { 220 try { 221 // Update is called from an external Thread : Synchronization is needed here 222 if(Thread.currentThread() != itsOwner.processor()) { 223 // Get the method to schedule a signal update 224 Method update = SignalListener.class.getDeclaredMethod( 225 "scheduleUpdate", new Class[] { ATSignal.class, ATMessage.class }); 226 227 // And schedule it for execution within the owning event loop 228 itsOwner.event_symbioticInvocation(this, 229 update, new ATObject[] { theParent, theUpdate }); 230 231 return true; 232 } 233 } catch (Exception e) { 234 // Could not select the method from this object; 235 e.printStackTrace(); 236 } 237 238 return false; 239 } 240 241 protected void propagateNewMessage(ATMessage updateMessage) { 242 for(Iterator dependentComputationIt = itsDependents.iterator(); 243 dependentComputationIt.hasNext();) { 244 SignalListener currentDependentComputation = (SignalListener) dependentComputationIt.next(); 245 246 currentDependentComputation.scheduleUpdate(this, updateMessage); 247 } 248 } 249 250 protected void propagateNewValues(ATTable computedValues) throws InterpreterException { 251 propagateNewMessage(new NATMethodInvocation( 252 Evaluator._APPLY_, 253 NATTable.of(computedValues), 254 NATTable.EMPTY)); 255 } 256 257 protected void propagateNewValue(ATObject computedValue) throws InterpreterException { 258 propagateNewValues(NATTable.of(computedValue)); 259 } 260}