1/* Copyright 2016 Software Freedom Conservancy Inc. 2 * 3 * This software is licensed under the GNU Lesser General Public License 4 * (version 2.1 or later). See the COPYING file in this distribution. 5 */ 6 7public class Geary.State.Machine : BaseObject { 8 9 /** The state machine's current state. */ 10 public uint state { get; private set; } 11 12 /** Determines if the state machine crashes your app when mis-configured. */ 13 public bool abort_on_no_transition { get; set; default = true; } 14 15 /** Determines if transition logging is enabled. */ 16 public bool logging { get; private set; default = false; } 17 18 private Geary.State.MachineDescriptor descriptor; 19 private Mapping[,] transitions; 20 private unowned Transition? default_transition; 21 private bool locked = false; 22 private unowned PostTransition? post_transition = null; 23 private void *post_user = null; 24 private Object? post_object = null; 25 private Error? post_err = null; 26 27 public Machine(MachineDescriptor descriptor, Mapping[] mappings, Transition? default_transition) { 28 this.descriptor = descriptor; 29 this.default_transition = default_transition; 30 31 // verify that each state and event in the mappings are valid 32 foreach (Mapping mapping in mappings) { 33 assert(mapping.state < descriptor.state_count); 34 assert(mapping.event < descriptor.event_count); 35 } 36 37 state = descriptor.start_state; 38 39 // build a transition map with state/event IDs (i.e. offsets) pointing directly into the 40 // map 41 transitions = new Mapping[descriptor.state_count, descriptor.event_count]; 42 for (int ctr = 0; ctr < mappings.length; ctr++) { 43 Mapping mapping = mappings[ctr]; 44 assert(transitions[mapping.state, mapping.event] == null); 45 transitions[mapping.state, mapping.event] = mapping; 46 } 47 } 48 49 public uint issue(uint event, void *user = null, Object? object = null, Error? err = null) { 50 assert(event < descriptor.event_count); 51 assert(state < descriptor.state_count); 52 53 unowned Mapping? mapping = transitions[state, event]; 54 55 unowned Transition? transition = (mapping != null) ? mapping.transition : default_transition; 56 if (transition == null) { 57 string msg = "%s: No transition defined for %s@%s".printf(to_string(), 58 descriptor.get_event_string(event), descriptor.get_state_string(state)); 59 60 if (this.abort_on_no_transition) 61 error(msg); 62 else 63 critical(msg); 64 65 return state; 66 } 67 68 // guard against reentrancy ... don't want to use a non-reentrant lock because then 69 // the machine will simply hang; assertion is better to ferret out design flaws 70 if (locked) { 71 error("Fatal reentrancy on locked state machine %s: %s", descriptor.name, 72 get_event_issued_string(state, event)); 73 } 74 locked = true; 75 76 uint old_state = state; 77 state = transition(state, event, user, object, err); 78 assert(state < descriptor.state_count); 79 80 if (!locked) { 81 error("Exited transition to unlocked state machine %s: %s", descriptor.name, 82 get_transition_string(old_state, event, state)); 83 } 84 locked = false; 85 86 if (this.logging) 87 message("%s: %s", to_string(), get_transition_string(old_state, event, state)); 88 89 // Perform post-transition if registered 90 if (post_transition != null) { 91 // clear post-transition before calling, in case this method is re-entered 92 unowned PostTransition? perform = post_transition; 93 void* perform_user = post_user; 94 Object? perform_object = post_object; 95 Error? perform_err = post_err; 96 97 post_transition = null; 98 post_user = null; 99 post_object = null; 100 post_err = null; 101 102 perform(perform_user, perform_object, perform_err); 103 } 104 105 return state; 106 } 107 108 // Must be used carefully: this allows for a delegate (callback) to be called after the 109 // *current* transition has occurred; thus, this method can *only* be called from within 110 // a TransitionHandler. 111 // 112 // Note that if a second call to this method is made inside the same transition, the earlier 113 // PostTransition is silently dropped. Only one PostTransition call may be registered. 114 // 115 // Returns false if unable to register the PostTransition delegate for the reasons specified 116 // above. 117 public bool do_post_transition(PostTransition post_transition, void *user = null, 118 Object? object = null, Error? err = null) { 119 if (!locked) { 120 warning("%s: Attempt to register post-transition while machine is unlocked", to_string()); 121 122 return false; 123 } 124 125 this.post_transition = post_transition; 126 post_user = user; 127 post_object = object; 128 post_err = err; 129 130 return true; 131 } 132 133 public string get_state_string(uint state) { 134 return descriptor.get_state_string(state); 135 } 136 137 public string get_event_string(uint event) { 138 return descriptor.get_event_string(event); 139 } 140 141 public string get_event_issued_string(uint state, uint event) { 142 return "%s@%s".printf(descriptor.get_state_string(state), descriptor.get_event_string(event)); 143 } 144 145 public string get_transition_string(uint old_state, uint event, uint new_state) { 146 return "%s@%s -> %s".printf(descriptor.get_state_string(old_state), descriptor.get_event_string(event), 147 descriptor.get_state_string(new_state)); 148 } 149 150 public string to_string() { 151 return "Machine %s [%s]".printf(descriptor.name, descriptor.get_state_string(state)); 152 } 153} 154 155