Erlang 21 - 3. gen_statem Behavior

3 जीन_स्टेम व्यवहार




erlang

3 जीन_स्टेम व्यवहार

यह खंड gen_statem(3) में gen_statem(3) मैनुअल पेज के साथ पढ़ा जाना है, जहां सभी इंटरफ़ेस फ़ंक्शंस और कॉलबैक फ़ंक्शन को विस्तार से वर्णित किया गया है।

3.1 घटना-चालित राज्य मशीनें

स्थापित ओटोमेटा थ्योरी से बहुत हद तक नहीं पता है कि राज्य का संक्रमण कैसे शुरू होता है, लेकिन यह मानता है कि आउटपुट इनपुट (और राज्य) का एक फ़ंक्शन है और वे कुछ प्रकार के मान हैं।

इवेंट-ड्रिव्ड स्टेट मशीन के लिए, इनपुट एक ऐसी घटना है जो एक राज्य संक्रमण को ट्रिगर करती है और आउटपुट राज्य संक्रमण के दौरान निष्पादित की जाने वाली क्रिया है। यह समरूप रूप से फिनाइट-स्टेट मशीन के गणितीय मॉडल के अनुरूप हो सकता है जिसे निम्नलिखित रूप के संबंधों के रूप में वर्णित किया जा सकता है:

State(S) x Event(E) -> Actions(A), State(S')

इन संबंधों की व्याख्या निम्न प्रकार से की जाती है: यदि हम राज्य S और E घटना होती है, तो हम A को क्रिया करते हैं और राज्य को S' संक्रमण S' बनाते हैं। ध्यान दें कि S' बराबर हो सकता है और A खाली हो सकता है।

जैसा कि A और S' केवल S और E पर निर्भर करते हैं, यहाँ वर्णित स्टेट मशीन एक मेयली मशीन है (उदाहरण के लिए, विकिपीडिया लेख "मेयली मशीन")।

अधिकांश gen_ आचरणों की तरह, gen_statem राज्य के अलावा एक सर्वर Data रखता है। इस वजह से, और जैसा कि राज्यों की संख्या पर कोई प्रतिबंध नहीं है (यह मानते हुए कि पर्याप्त वर्चुअल मशीन मेमोरी है) या विभिन्न इनपुट घटनाओं की संख्या पर, इस व्यवहार के साथ लागू की गई एक राज्य मशीन वास्तव में पूरी तरह से ट्यूरिंग है। लेकिन यह ज्यादातर इवेंट-ड्रिव्ड मेयली मशीन जैसा लगता है।

3.2 gen_statem का उपयोग कब करें

यदि आपकी प्रक्रिया तर्क राज्य मशीन के रूप में वर्णन करने के लिए सुविधाजनक है, और आप इनमें से कोई भी gen_statem प्रमुख विशेषताएं चाहते हैं:

  • Event Type (जैसे कॉल , कास्ट और जानकारी ) की परवाह किए बिना, प्रत्येक राज्य के लिए सह-स्थित कॉलबैक कोड
  • Postponing Events (चयनात्मक प्राप्त करने का विकल्प)
  • Inserted Events जो हैं: राज्य मशीन से स्वयं की घटनाओं (विशेष रूप से आंतरिक घटनाओं में)
  • State Enter Calls (प्रत्येक राज्य के कॉलबैक कोड के बाकी हिस्सों के साथ राज्य प्रवेश पर कॉलबैक)
  • उपयोग में आसान टाइमआउट ( State Time-Outs , Event Time-Outs और Generic Time-outs (नाम समय-आउट)

यदि ऐसा है, या संभवतः भविष्य के संस्करणों में आवश्यक है, तो आपको gen_statem का उपयोग gen_statem पर करने पर विचार करना चाहिए।

सरल राज्य मशीनों के लिए इन सुविधाओं की जरूरत नहीं है gen_server ठीक काम करता है। इसे छोटी कॉल ओवरहेड भी मिली है, लेकिन हम यहां 2 बनाम 3.3 माइक्रोसेकंड कॉल जैसे कुछ के बारे में बात कर रहे हैं, इसलिए यदि कॉलबैक केवल जवाब देने की तुलना में थोड़ा अधिक करता है, या यदि कॉल अत्यधिक बार-बार होता है, तो यह अंतर नहीं है नोटिस करना मुश्किल होगा।

3.3 कॉलबैक मॉड्यूल

कॉलबैक मॉड्यूल में फ़ंक्शंस होते हैं जो राज्य मशीन को लागू करते हैं। जब कोई घटना होती है, तो gen_statem व्यवहार इंजन कॉलबैक मॉड्यूल में घटना, वर्तमान स्थिति और सर्वर डेटा के साथ एक फ़ंक्शन को कॉल करता है। यह फ़ंक्शन इस ईवेंट के लिए क्रियाएँ करता है, और नए राज्य और सर्वर डेटा को वापस करता है और व्यवहार इंजन द्वारा किए जाने वाले कार्य भी करता है।

व्यवहार इंजन में राज्य मशीन की स्थिति, सर्वर डेटा, टाइमर संदर्भ, पोस्पोन्ड संदेशों और अन्य मेटाडेटा की एक कतार होती है। यह सभी प्रक्रिया संदेश प्राप्त करता है, सिस्टम संदेशों को संभालता है, और मशीन विशिष्ट घटनाओं के साथ कॉलबैक मॉड्यूल को कॉल करता है।

3.4 कॉलबैक मोड

gen_statem व्यवहार दो कॉलबैक मोड का समर्थन करता है:

state_functions

घटनाओं को प्रति राज्य एक कॉलबैक फ़ंक्शन द्वारा नियंत्रित किया जाता है।

handle_event_function

घटनाओं को एक एकल कॉलबैक फ़ंक्शन द्वारा नियंत्रित किया जाता है।

कॉलबैक मोड को सर्वर स्टार्ट पर चुना जाता है और कोड अपग्रेड / डाउनग्रेड के साथ बदला जा सकता है।

अनुभाग Event Handler देखें जो कॉलबैक फ़ंक्शन (इवेंट्स) को संभालने वाले ईवेंट का वर्णन करता है।

कॉलबैक मोड एक अनिवार्य कॉलबैक फ़ंक्शन Module:callback_mode() को लागू करके चुना जाता है जो कॉलबैक मोड में से एक को लौटाता है।

Module:callback_mode() फ़ंक्शन कॉलबैक मोड और एटम state_enter वाली एक सूची भी लौटा सकता है, जिस स्थिति में कॉलबैक मोड के लिए State Enter Calls सक्रिय होते हैं।

कॉलबैक मोड चुनना

संक्षिप्त संस्करण: state_functions चुनें - यह gen_fsm तरह सबसे अधिक है। लेकिन अगर आप यह नहीं चाहते हैं कि राज्य एक परमाणु होना चाहिए, या यदि आप प्रति राज्य एक घटना हैंडलर फ़ंक्शन लिखना नहीं चाहते हैं; कृपया पढ़ें ...

दो Callback Modes अलग-अलग संभावनाएं और प्रतिबंध देते हैं, एक सामान्य लक्ष्य के साथ: घटनाओं और राज्यों के सभी संभावित संयोजनों को संभालने के लिए।

यह किया जा सकता है, उदाहरण के लिए, समय पर एक राज्य पर ध्यान केंद्रित करके और प्रत्येक राज्य के लिए यह सुनिश्चित करना कि सभी घटनाओं को संभाला जाए। वैकल्पिक रूप से, आप समय पर एक घटना पर ध्यान केंद्रित कर सकते हैं और यह सुनिश्चित कर सकते हैं कि यह प्रत्येक राज्य में नियंत्रित किया जाता है। आप इन रणनीतियों का मिश्रण भी उपयोग कर सकते हैं।

state_functions साथ, आप केवल परमाणु-राज्यों का उपयोग करने के लिए प्रतिबंधित हैं, और gen_statem इंजन शाखाएँ आपके लिए राज्य के नाम पर निर्भर करती हैं। यह कॉलबैक मॉड्यूल को कोड में एक ही स्थान पर विशेष रूप से एक राज्य में सभी ईवेंट क्रियाओं के कार्यान्वयन का सह-पता लगाने के लिए प्रोत्साहित करता है, इसलिए उस समय एक राज्य पर ध्यान केंद्रित करना चाहिए।

जब आप इस अध्याय में नियमित रूप से राज्य का आरेख रखते हैं, तो यह विधा अच्छी तरह से फिट होती है, जो उस राज्य के चारों ओर एक राज्य से संबंधित सभी घटनाओं और कार्यों का वर्णन करती है, और प्रत्येक राज्य का विशिष्ट नाम होता है।

handle_event_function साथ, आप रणनीति मिश्रण करने के लिए स्वतंत्र हैं, क्योंकि सभी घटनाओं और राज्यों को एक ही कॉलबैक फ़ंक्शन में नियंत्रित किया जाता है।

यह मोड उस समय समान रूप से अच्छी तरह से काम करता है जब आप उस समय किसी एक घटना पर या एक राज्य पर ध्यान केंद्रित करना चाहते हैं, लेकिन फ़ंक्शन Module:handle_event/4 जल्दी से बहुत बड़ा हो जाता है, ताकि वह बिना किसी कार्य के मदद करने के लिए बड़ी हो सके।

मोड गैर-परमाणु राज्यों के उपयोग को सक्षम करता है, उदाहरण के लिए, जटिल राज्य या यहां तक ​​कि पदानुक्रमित राज्य। अनुभाग Complex State देखें। यदि, उदाहरण के लिए, एक स्टेट डायग्राम क्लाइंट साइड और प्रोटोकॉल के सर्वर साइड के लिए काफी हद तक एक जैसा है, तो आपके पास एक स्टेट {StateName,server} या {StateName,client} , और StateName यह निर्धारित कर सकते हैं कि कोड को कहां संभालना है। राज्य में अधिकांश कार्यक्रम। ट्यूपल का दूसरा तत्व तब यह चुनने के लिए उपयोग किया जाता है कि क्या विशेष क्लाइंट-साइड या सर्वर-साइड घटनाओं को संभालना है या नहीं।

3.5 इवेंट हैंडलर

कॉलबैक फ़ंक्शन जो किसी ईवेंट को हैंडल करता है, कॉलबैक मोड पर निर्भर करता है:

state_functions
इस घटना से निपटने के लिए है:
Module:StateName(EventType, EventContent, Data)

यह फ़ॉर्म ज्यादातर Example अनुभाग में उपयोग किया जाने वाला एक है।

handle_event_function
इस घटना से निपटने के लिए है:
Module:handle_event(EventType, EventContent, State, Data)

उदाहरण के लिए अनुभाग One Event Handler देखें।

राज्य या तो फ़ंक्शन का नाम है या इसके लिए एक तर्क है। अन्य तर्क Event Types , ईवेंट पर निर्भर EventContent , और वर्तमान सर्वर Data में वर्णित EventType हैं।

स्टेट एंट्री कॉल भी ईवेंट हैंडलर द्वारा नियंत्रित की जाती है और इसमें कुछ अलग तर्क होते हैं। अनुभाग देखें State Enter Calls

ईवेंट हैंडलर रिटर्न मान को Module:StateName/3 में gen_statem मैनुअल पेज के विवरण में परिभाषित किया गया है, लेकिन यहां एक अधिक पठनीय सूची है:

{next_state, NextState, NewData, Actions}
{next_state, NextState, NewData}

अगली स्थिति सेट करें और सर्वर डेटा अपडेट करें। यदि Actions फ़ील्ड का उपयोग किया जाता है, तो राज्य संक्रमण क्रियाओं को निष्पादित करें। खाली Actions लिस्ट फ़ील्ड को वापस न करने के बराबर है।

संभावित राज्य संक्रमण कार्यों की सूची के लिए अनुभाग State Transition Actions अधिनियम देखें।

यदि NextState =/= State राज्य मशीन एक नए राज्य में बदलती है। यदि सक्षम और सभी postponed events को फिर से लिया जाता है, तो एक state enter call किया जाता है।

{keep_state, NewData, Actions}
{keep_state, NewData}

NextState =:= State साथ next_state मानों के रूप में भी, कोई भी राज्य परिवर्तन नहीं है।

{keep_state_and_data, Actions}
keep_state_and_data

keep_state साथ keep_state मानों के समान है NextData =:= Data , अर्थात, सर्वर डेटा में कोई परिवर्तन नहीं।

{repeat_state, NewData, Actions}
{repeat_state, NewData}
{repeat_state_and_data, Actions}
repeat_state_and_data

keep_state या keep_state_and_data मानों के रूप में भी, और यदि State Enter Calls सक्षम हैं, तो इस राज्य को फिर से दर्ज किए जाने के रूप में राज्य में दर्ज करें कॉल को दोहराएं।

{stop, Reason, NewData}
{stop, Reason}

कारण सहित सर्वर बंद करो। यदि NewData फ़ील्ड का उपयोग किया जाता है, तो पहले सर्वर डेटा को अपडेट करें।

{stop_and_reply, Reason, NewData, ReplyActions}
{stop_and_reply, Reason, ReplyActions}

stop मान के समान, लेकिन पहले दिए गए राज्य संक्रमण कार्यों को निष्पादित करें जो केवल उत्तर क्रियाएं हो सकती हैं।

पहला राज्य

पहला राज्य तय करने के लिए Module:init(Args) कॉलबैक फ़ंक्शन को किसी भी Event Handler को कॉल करने से पहले कहा जाता है। यह फ़ंक्शन एक ईवेंट हैंडलर फ़ंक्शन की तरह व्यवहार करता है, लेकिन इसका एकमात्र तर्क Args है gen_statem start/3,4 या start_link/3,4 फ़ंक्शन से, और {ok, State, Data} या {ok, State, Data, Actions} gen_statem {ok, State, Data} लौटाता है। । यदि आप इस फ़ंक्शन से postpone कार्रवाई का उपयोग करते हैं, तो उस कार्रवाई को अनदेखा कर दिया जाता है, क्योंकि पोस्टपोन की कोई घटना नहीं है।

3.6 राज्य संक्रमण कार्रवाई

पहले खंड में Event-Driven State Machines क्रियाओं को सामान्य राज्य मशीन मॉडल के एक भाग के रूप में उल्लेख किया गया था। इन सामान्य क्रियाओं को कोड के साथ लागू किया जाता है जो कि कॉलबैक मॉड्यूल gen_statem एक इवेंट-हैंडलिंग कॉलबैक फ़ंक्शन में gen_statem इंजन पर लौटने से पहले निष्पादित होती है।

अधिक विशिष्ट राज्य-संक्रमण क्रियाएं हैं जो कॉलबैक फ़ंक्शन कॉलबैक फ़ंक्शन रिटर्न के बाद करने के लिए gen_statem इंजन को कमांड कर सकती हैं। callback function से return value में actions की एक सूची वापस करके ये आदेश दिए गए हैं। ये संभावित राज्य संक्रमण क्रियाएं हैं:

postpone
{postpone, Boolean}
यदि वर्तमान घटना को स्थगित कर दिया जाए, तो अनुभाग Postponing Events
hibernate
{hibernate, Boolean}
यदि gen_statem हाइबरनेट करते हैं, तो अनुभाग Hibernation में इलाज किया जाता है
{state_timeout, Time}
{state_timeout, Time, Opts}
State Time-Outs शुरू करें, सेक्शन State Time-Outs में अधिक पढ़ें
{{timeout, Name}, Time}
{{timeout, Name}, Time, Opts}
जेनेरिक टाइम-आउट शुरू करें, सेक्शन Generic Time-Outs में अधिक पढ़ें
{timeout, Time}
{timeout, Time, Opts}
Time
ईवेंट टाइम-आउट प्रारंभ करें, अनुभाग Event Time-Outs में और देखें
{reply, From, Reply}
एक कॉल करने वाले का उत्तर दें, अनुभाग के अंत में उल्लेख किया गया है
{next_event, EventType, EventContent}
अगली घटना को संभालने के लिए उत्पन्न करें, अनुभाग Inserted Events

विवरण के लिए, टाइप action() लिए gen_statem(3) मैनुअल पेज देखें action() । आप उदाहरण के लिए, कई कॉल करने वालों को जवाब दे सकते हैं, कई अगली घटनाओं को उत्पन्न कर सकते हैं, और सापेक्ष समय के बजाय निरपेक्ष का उपयोग करने के लिए एक टाइम-आउट सेट कर सकते हैं ( Opts फ़ील्ड का उपयोग करके)।

3.7 घटना प्रकार

ईवेंट को विभिन्न event types में वर्गीकृत किया जाता है। सभी प्रकार के ईवेंट एक कॉलबैक फ़ंक्शन में दिए गए दिए गए स्टेट के लिए हैं, और उस फ़ंक्शन को EventType और EventContent को तर्कों के साथ प्राप्त होता है।

निम्नलिखित घटना प्रकारों की पूरी सूची है और वे कहाँ से आते हैं:

cast
gen_statem:cast द्वारा gen_statem:cast
{call,From}
gen_statem:call द्वारा gen_statem:call , जहाँ From उत्तर पता का उपयोग तब किया जाता है जब राज्य संक्रमण कार्रवाई {reply,From,Msg} या फिर gen_statem:reply कॉल gen_statem:reply
info
gen_statem प्रक्रिया को भेजे गए किसी भी नियमित प्रक्रिया संदेश द्वारा उत्पन्न।
state_timeout
राज्य संक्रमण कार्रवाई {state_timeout,Time,EventContent} राज्य टाइमर समय से उत्पन्न।
{timeout,Name}
राज्य संक्रमण कार्रवाई {{timeout,Name},Time,EventContent} द्वारा उत्पन्न जेनेरिक टाइमर समय से बाहर।
timeout
राज्य संक्रमण कार्रवाई {timeout,Time,EventContent} (या इसका संक्षिप्त रूप Time ) इवेंट टाइमर टाइमिंग द्वारा उत्पन्न।
internal
राज्य संक्रमण action {next_event,internal,EventContent} द्वारा उत्पन्न। उपरोक्त सभी प्रकार के प्रकार {next_event,EventType,EventContent} का उपयोग करके भी उत्पन्न किए जा सकते हैं।

3.8 राज्य प्रवेश कॉल

अगर कॉलबैक मोड की परवाह किए बिना यह gen_statem व्यवहार हो सकता है, तो जब भी राज्य बदलता है, तब विशेष तर्क के साथ call the state callback को स्वचालित रूप से call the state callback ताकि आप बाकी राज्य संक्रमण नियमों के पास राज्य में प्रवेश की कार्रवाई लिख सकें। यह आमतौर पर इस तरह दिखता है:

StateName(enter, OldState, Data) ->
    ... code for state enter actions here ...
    {keep_state, NewData};
StateName(EventType, EventContent, Data) ->
    ... code for actions here ...
    {next_state, NewStateName, NewData}.

चूंकि राज्य में प्रवेश कॉल एक घटना नहीं है इसलिए अनुमत वापसी मूल्य और State Transition Actions पर प्रतिबंध हैं। आप राज्य को बदल नहीं सकते हैं, इस गैर-घटना को postpone , या insert events

दर्ज किया गया पहला राज्य वर्तमान स्थिति के बराबर OldState साथ एक राज्य प्रवेश कॉल प्राप्त करेगा।

आप Event Handler से {repeat_state,...} रिटर्न वैल्यू का उपयोग करके स्टेट एंटर कॉल को दोहरा सकते हैं। इस मामले में OldState भी वर्तमान स्थिति के बराबर होगा।

आपकी राज्य मशीन कैसे निर्दिष्ट की जाती है, इस पर निर्भर करते हुए, यह एक बहुत ही उपयोगी विशेषता हो सकती है, लेकिन यह आपको सभी राज्यों में स्टेट एंटर कॉल को संभालने के लिए मजबूर करती है। State Enter Actions चैप्टर भी देखें।

३.९ उदाहरण

कोड लॉक वाले दरवाजे को राज्य मशीन के रूप में देखा जा सकता है। प्रारंभ में, दरवाजा बंद है। जब कोई बटन दबाता है, तो एक घटना उत्पन्न होती है। दबाए गए बटन एकत्र किए जाते हैं, सही कोड में बटन की संख्या तक। यदि सही है, तो दरवाजा 10 सेकंड के लिए अनलॉक किया गया है। यदि सही नहीं है, तो हम एक नया बटन दबाए जाने की प्रतीक्षा करते हैं।

चित्र 3.1: कोड लॉक स्टेट डायग्राम

इस कोड लॉक स्टेट मशीन को निम्नलिखित कॉलबैक मॉड्यूल के साथ gen_statem का उपयोग करके लागू किया जा सकता है:

-module(code_lock).
-behaviour(gen_statem).
-define(NAME, code_lock).

-export([start_link/1]).
-export([button/1]).
-export([init/1,callback_mode/0,terminate/3]).
-export([locked/3,open/3]).

start_link(Code) ->
    gen_statem:start_link({local,?NAME}, ?MODULE, Code, []).

button(Button) ->
    gen_statem:cast(?NAME, {button,Button}).

init(Code) ->
    do_lock(),
    Data = #{code => Code, length => length(Code), buttons => []},
    {ok, locked, Data}.

callback_mode() ->
    state_functions.
locked(
  cast, {button,Button},
  #{code := Code, length := Length, buttons := Buttons} = Data) ->
    NewButtons =
        if
            length(Buttons) < Length ->
                Buttons;
            true ->
                tl(Buttons)
        end ++ [Button],
    if
        NewButtons =:= Code -> % Correct
	    do_unlock(),
            {next_state, open, Data#{buttons := []},
             [{state_timeout,10000,lock}]}; % Time in milliseconds
	true -> % Incomplete | Incorrect
            {next_state, locked, Data#{buttons := NewButtons}}
    end.
open(state_timeout, lock,  Data) ->
    do_lock(),
    {next_state, locked, Data};
open(cast, {button,_}, Data) ->
    {next_state, open, Data}.
do_lock() ->
    io:format("Lock~n", []).
do_unlock() ->
    io:format("Unlock~n", []).

terminate(_Reason, State, _Data) ->
    State =/= locked andalso do_lock(),
    ok.

कोड को अगले खंडों में समझाया गया है।

3.10 शुरू gen_statem

पिछले अनुभाग में उदाहरण में, gen_statem को code_lock:start_link(Code) कहकर शुरू किया गया है:

start_link(Code) ->
    gen_statem:start_link({local,?NAME}, ?MODULE, Code, []).

start_link कॉल फ़ंक्शन gen_statem:start_link/4 , जो एक नई प्रक्रिया, एक gen_statem को जन्म देती है और लिंक gen_statem

  • पहला तर्क, {local,?NAME} , नाम निर्दिष्ट करता है। इस स्थिति में, gen_statem स्थानीय रूप से मैक्रो के माध्यम से code_lock रूप में पंजीकृत है ?NAME

    यदि नाम छोड़ा गया है, तो gen_statem पंजीकृत नहीं है। इसके बजाय इसके पिड का उपयोग किया जाना चाहिए। नाम को {global,Name} रूप में भी निर्दिष्ट किया जा सकता है, फिर gen_statem को कर्नेल में global:register_name/2 का उपयोग करके पंजीकृत किया जाता है।

  • दूसरा तर्क; ?MODULE , कॉलबैक मॉड्यूल का नाम है, अर्थात, वह मॉड्यूल जहां कॉलबैक फ़ंक्शन स्थित हैं, जो यह मॉड्यूल है।

    इंटरफ़ेस फ़ंक्शन ( start_link/1 और button/1 ) कॉलबैक फ़ंक्शन ( init/1 , locked/3 , और open/3 ) के रूप में एक ही मॉड्यूल में स्थित हैं। क्लाइंट-साइड कोड और सर्वर-साइड कोड एक मॉड्यूल में होना सामान्य रूप से अच्छा प्रोग्रामिंग अभ्यास है।

  • तीसरा तर्क, Code , अंकों की एक सूची है, जो कि सही अनलॉक कोड है जिसे कॉलबैक फ़ंक्शन init/1

  • चौथा तर्क, [] , विकल्पों की एक सूची है। उपलब्ध विकल्पों के लिए, gen_statem:start_link/3

यदि नाम पंजीकरण सफल होता है, तो नई gen_statem प्रक्रिया कॉलबैक फ़ंक्शन code_lock:init(Code) कॉल करती है। इस फ़ंक्शन से {ok, State, Data} वापस लौटने की उम्मीद है, जहां State gen_statem की प्रारंभिक स्थिति है, इस मामले में locked ; यह मानते हुए कि दरवाजा शुरू करने के लिए बंद है। Data gen_statem का आंतरिक सर्वर डेटा है। यहां सर्वर डेटा कुंजी code साथ एक map जो सही बटन अनुक्रम को संग्रहीत करता है, कुंजी length इसकी लंबाई को संग्रहीत करता है, और कुंजी buttons जो समान buttons को संग्रहीत करता है।

init(Code) ->
    do_lock(),
    Data = #{code => Code, length => length(Code), buttons => []},
    {ok, locked, Data}.

फंक्शन gen_statem:start_link सिंक्रोनस है। यह तब तक नहीं लौटता है जब तक कि gen_statem को आरंभीकृत नहीं किया जाता है और यह घटनाओं को प्राप्त करने के लिए तैयार है।

फ़ंक्शन gen_statem:start_link का उपयोग किया जाना चाहिए यदि gen_statem एक पर्यवेक्षण ट्री का हिस्सा है, जो कि पर्यवेक्षक द्वारा शुरू किया गया है। एक अन्य फ़ंक्शन, gen_statem:start का उपयोग स्टैंडअलोन gen_statem को शुरू करने के लिए किया जा सकता है, gen_statem , एक gen_statem जो पर्यवेक्षण पेड़ का हिस्सा नहीं है।

फ़ंक्शन Module:callback_mode/0 state_functions Module:callback_mode/0 कॉलबैक मॉड्यूल के लिए state_functions का चयन करता है, इस स्थिति में state_functions । यही है, प्रत्येक राज्य को अपना स्वयं का हैंडलर फ़ंक्शन मिला है:

callback_mode() ->
    state_functions.

3.11 हैंडलिंग इवेंट

बटन घटना के बारे में कोड लॉक को सूचित करने वाला कार्य gen_statem:cast/2 का उपयोग करके लागू किया gen_statem:cast/2 :

button(Digit) ->
    gen_statem:cast(?NAME, {button,Digit}).

पहला तर्क gen_statem का नाम है और इसे शुरू करने के लिए उपयोग किए गए नाम से सहमत होना चाहिए। इसलिए, हम उसी मैक्रो का उपयोग करते हैं ?NAME जब शुरू होता है। {button,Digit} घटना सामग्री है।

घटना gen_statem को भेजी gen_statem । जब घटना प्राप्त होती है, तो gen_statem StateName(cast, Event, Data) कॉल करता है, जिसे टपल {next_state, NewStateName, NewData} , या {next_state, NewStateName, NewData, Actions} StateName वर्तमान स्थिति का नाम है और StateName जाने वाले अगले राज्य का नाम है। NewData , NewData के सर्वर डेटा के लिए एक नया मान है, और Actions gen_statem इंजन द्वारा किए जाने वाले कार्यों की एक सूची है।

locked(
  cast, {button,Button},
  #{code := Code, length := Length, buttons := Buttons} = Data) ->
    NewButtons =
        if
            length(Buttons) < Length ->
                Buttons;
            true ->
                tl(Buttons)
        end ++ [Button],
    if
        NewButtons =:= Code -> % Correct
	    do_unlock(),
            {next_state, open, Data#{buttons := []},
             [{state_timeout,10000,lock}]}; % Time in milliseconds
	true -> % Incomplete | Incorrect
            {next_state, locked, Data#{buttons := NewButtons}}
    end.

राज्य locked , जब एक बटन दबाया जाता है, तो इसे सही कोड की लंबाई तक अंतिम दबाए गए बटन के साथ एकत्र किया जाता है, और सही कोड के साथ तुलना की जाती है। परिणाम के आधार पर, दरवाजा या तो अनलॉक हो जाता है और gen_statem open चला जाता है, या दरवाजा locked रहता है।

स्टेट open बदलने पर, एकत्रित बटन रीसेट हो जाते हैं, लॉक अनलॉक हो जाता है, और 10 एस के लिए एक स्टेट टाइमर शुरू हो जाता है।

open(cast, {button,_}, Data) ->
    {next_state, open, Data}.

राज्य open , एक बटन घटना को उसी राज्य में रहने से अनदेखा किया जाता है। यह {keep_state, Data} या इस मामले में भी किया जा सकता है क्योंकि Data अपरिवर्तित भी keep_state_and_data

3.12 स्टेट टाइम-आउट

जब एक सही कोड दिया गया है, तो दरवाजा अनलॉक किया गया है और निम्नलिखित ट्यूपल को locked/2 से लौटाया गया है:

{next_state, open, Data#{buttons := []},
 [{state_timeout,10000,lock}]}; % Time in milliseconds

10,000 मिलीसेकंड में एक टाइम-आउट मान है। इस समय (10 सेकंड) के बाद, एक टाइम-आउट होता है। फिर, StateName(state_timeout, lock, Data) कहा जाता है। टाइम-आउट तब होता है जब दरवाजा 10 सेकंड के लिए open । उसके बाद दरवाजा फिर से बंद कर दिया जाता है:

open(state_timeout, lock,  Data) ->
    do_lock(),
    {next_state, locked, Data};

जब स्टेट मशीन बदलती है तो स्टेट टाइम-आउट के लिए टाइमर स्वचालित रूप से रद्द हो जाता है। आप एक नए समय के लिए इसे सेट करके एक स्टेट टाइम-आउट को पुनरारंभ कर सकते हैं, जो रनिंग टाइमर को रद्द कर देता है और एक नया शुरू करता है। इसका तात्पर्य यह है कि आप एक स्टेट टाइम-आउट को समय infinity साथ पुनः आरंभ करके रद्द कर सकते हैं।

3.13 सभी राज्य कार्यक्रम

कभी-कभी घटनाएं gen_statem किसी भी राज्य में आ सकती हैं। एक सामान्य राज्य हैंडलर फ़ंक्शन में इन्हें संभालना सुविधाजनक है कि सभी राज्य फ़ंक्शन राज्य के लिए विशिष्ट नहीं होने वाली घटनाओं के लिए कॉल करते हैं।

एक code_length/0 फ़ंक्शन पर विचार करें जो सही कोड की लंबाई लौटाता है। हम उन सभी घटनाओं को प्रेषित करते हैं जो सामान्य फ़ंक्शन handle_common/3 लिए राज्य-विशिष्ट नहीं हैं:

...
-export([button/1,code_length/0]).
...

code_length() ->
    gen_statem:call(?NAME, code_length).

...
locked(...) -> ... ;
locked(EventType, EventContent, Data) ->
    handle_common(EventType, EventContent, Data).

...
open(...) -> ... ;
open(EventType, EventContent, Data) ->
    handle_common(EventType, EventContent, Data).

handle_common({call,From}, code_length, #{code := Code} = Data) ->
    {keep_state, Data,
     [{reply,From,length(Code)}]}.

इसे करने का एक और तरीका एक सुविधा मैक्रो के माध्यम से है ?HANDLE_COMMON/0 :

...
-export([button/1,code_length/0]).
...

code_length() ->
    gen_statem:call(?NAME, code_length).

-define(HANDLE_COMMON,
    ?FUNCTION_NAME(T, C, D) -> handle_common(T, C, D)).
%%
handle_common({call,From}, code_length, #{code := Code} = Data) ->
    {keep_state, Data,
     [{reply,From,length(Code)}]}.

...
locked(...) -> ... ;
?HANDLE_COMMON.

...
open(...) -> ... ;
?HANDLE_COMMON.

यह उदाहरण gen_statem:call/2 का उपयोग करता है, जो सर्वर से उत्तर की प्रतीक्षा करता है। उत्तर को एक {keep_state, ...} {reply,From,Reply} भेजा जाता है {reply,From,Reply} एक क्रिया सूची में {keep_state, ...} tuple के साथ भेजा जाता है जो वर्तमान स्थिति को बनाए रखता है। यह रिटर्न फॉर्म सुविधाजनक है जब आप वर्तमान स्थिति में रहना चाहते हैं, लेकिन यह नहीं जानते हैं कि यह क्या है या इसकी परवाह नहीं करता है।

यदि सामान्य ईवेंट हैंडलर को वर्तमान स्थिति को जानना आवश्यक है, तो इसके बजाय एक handle_common/4 का उपयोग किया जा सकता है:

-define(HANDLE_COMMON,
    ?FUNCTION_NAME(T, C, D) -> handle_common(T, C, ?FUNCTION_NAME, D)).

3.14 एक घटना हैंडलर

यदि Callback Mode handle_event_function का उपयोग किया जाता है, तो सभी ईवेंट्स को Module:handle_event/4 में हैंडल किया जाता है Module:handle_event/4 और हम एक इवेंट-केंद्रित दृष्टिकोण का उपयोग कर सकते हैं (जहां हमें) इवेंट के आधार पर पहले शाखा और फिर स्टेट पर निर्भर करता है:

...
-export([handle_event/4]).

...
callback_mode() ->
    handle_event_function.

handle_event(cast, {button,Digit}, State, #{code := Code} = Data) ->
    case State of
	locked ->
            #{length := Length, buttons := Buttons} = Data,
            NewButtons =
                if
                    length(Buttons) < Length ->
                        Buttons;
                    true ->
                        tl(Buttons)
                end ++ [Button],
            if
                NewButtons =:= Code -> % Correct
                    do_unlock(),
                    {next_state, open, Data#{buttons := []},
                     [{state_timeout,10000,lock}]}; % Time in milliseconds
                true -> % Incomplete | Incorrect
                    {keep_state, Data#{buttons := NewButtons}}
            end;
	open ->
            keep_state_and_data
    end;
handle_event(state_timeout, lock, open, Data) ->
    do_lock(),
    {next_state, locked, Data};
handle_event(
  {call,From}, code_length, _State, #{code := Code} = Data) ->
    {keep_state, Data,
     [{reply,From,length(Code)}]}.

...

3.15 रोकना

एक पर्यवेक्षण वृक्ष में

यदि gen_statem एक पर्यवेक्षण वृक्ष का हिस्सा है, तो कोई स्टॉप फ़ंक्शन की आवश्यकता नहीं है। gen_statem अपने पर्यवेक्षक द्वारा स्वचालित रूप से समाप्त हो जाता है। वास्तव में यह कैसे किया जाता है यह पर्यवेक्षक में निर्धारित shutdown strategy द्वारा परिभाषित किया गया है।

यदि समाप्ति से पहले सफाई करना आवश्यक है, तो शटडाउन रणनीति का समय-आउट मान होना चाहिए और gen_statem को कार्य init/1 में होना चाहिए, जो process_flag(trap_exit, true) कहकर निकास संकेतों को फंसाने के लिए निर्धारित होता है:

init(Args) ->
    process_flag(trap_exit, true),
    do_lock(),
    ...

जब शट डाउन करने का आदेश दिया जाता है, तो gen_statem तब कॉलबैक फ़ंक्शन terminate(shutdown, State, Data) करता terminate(shutdown, State, Data)

इस उदाहरण में, फ़ंक्शन terminate/3 दरवाजा खुला रहता है अगर यह खुला है, तो हम गलती से पर्यवेक्षण का पेड़ समाप्त होने पर दरवाजा खुला नहीं छोड़ते हैं:

terminate(_Reason, State, _Data) ->
    State =/= locked andalso do_lock(),
    ok.

स्टैंडअलोन gen_statem

यदि gen_statem पर्यवेक्षण वृक्ष का हिस्सा नहीं है, तो इसे gen_statem:stop का उपयोग करके रोका जा सकता है gen_statem:stop , अधिमानतः एपीआई फ़ंक्शन के माध्यम से:

...
-export([start_link/1,stop/0]).

...
stop() ->
    gen_statem:stop(?NAME).

यह एक पर्यवेक्षित सर्वर के लिए gen_statem कॉल कॉलबैक फ़ंक्शन को terminate/3 तरह करता है और प्रक्रिया को समाप्त करने की प्रतीक्षा करता है।

3.16 इवेंट टाइम-आउट

gen_statem की पूर्ववर्ती gen_fsm से विरासत में मिली एक टाइम-आउट सुविधा, एक इवेंट टाइम-आउट है, यानी अगर कोई इवेंट आता है तो टाइमर रद्द हो जाता है। आप या तो एक घटना या एक टाइम-आउट प्राप्त करते हैं, लेकिन दोनों नहीं।

यह राज्य संक्रमण कार्रवाई {timeout,Time,EventContent} , या सिर्फ एक पूर्णांक Time द्वारा आदेश दिया जाता है, यहां तक ​​कि संलग्न कार्रवाई सूची के बिना भी (उत्तरार्द्ध gen_fsm से विरासत में मिला एक रूप है।

इस प्रकार का टाइम-आउट निष्क्रियता पर कार्य करने के लिए उदाहरण के लिए उपयोगी है। यदि 30 सेकंड के लिए कोई बटन नहीं दबाया जाता है तो हम कोड अनुक्रम को फिर से शुरू करते हैं:

...

locked(timeout, _, Data) ->
    {next_state, locked, Data#{buttons := []}};
locked(
  cast, {button,Digit},
  #{code := Code, length := Length, buttons := Buttons} = Data) ->
...
	true -> % Incomplete | Incorrect
            {next_state, locked, Data#{buttons := NewButtons},
             30000} % Time in milliseconds
...

जब भी हमें एक बटन ईवेंट प्राप्त होता है, तो हम 30 सेकंड का ईवेंट टाइम-आउट शुरू करते हैं, और यदि हमें कोई ईवेंट प्रकार timeout मिलता है, तो हम शेष कोड अनुक्रम रीसेट करते हैं।

एक ईवेंट टाइम-आउट किसी अन्य ईवेंट द्वारा रद्द कर दिया जाता है ताकि आपको या तो कोई अन्य ईवेंट मिल जाए या टाइम-आउट ईवेंट मिल जाए। इसलिए यह संभव नहीं है और न ही किसी इवेंट टाइम-आउट को रद्द करने या फिर से शुरू करने की आवश्यकता है। आप जिस भी घटना पर कार्रवाई करते हैं वह पहले ही इवेंट का समय रद्द कर चुकी है ...

ध्यान दें कि जब आपके पास All State Events में स्टेटस कॉल या उदाहरण के लिए, अज्ञात ईवेंट को हैंडल करने के लिए इवेंट टाइम-आउट ठीक से काम नहीं करता है, क्योंकि सभी तरह के ईवेंट ईवेंट टाइम-आउट को रद्द कर देंगे।

3.17 जेनेरिक टाइम-आउट

राज्य समय-बहिष्कार का पिछला उदाहरण केवल तभी काम करता है जब राज्य मशीन समय-आउट समय के दौरान उसी स्थिति में रहती है। और घटना समय-बहिष्कार केवल तभी काम करता है जब कोई परेशान करने वाली असंबंधित घटनाएं न हों।

आप एक राज्य में एक टाइमर शुरू करना चाहते हैं और दूसरे में टाइम-आउट का जवाब दे सकते हैं, हो सकता है कि बदलते राज्यों के बिना टाइम-आउट को रद्द कर दें, या शायद समानांतर में कई बार-आउट चलाएं। यह सब generic time-outs साथ पूरा किया जा सकता है। वे event time-outs तरह थोड़ा सा देख सकते हैं लेकिन उनमें से किसी भी संख्या के लिए एक साथ अनुमति देने के लिए एक नाम होता है और वे स्वचालित रूप से रद्द नहीं होते हैं।

यहाँ open उदाहरण के लिए जेनेरिक टाइम-आउट का उपयोग करके इसके बजाय पिछले उदाहरण में स्थिति टाइम-आउट को पूरा करने का तरीका बताया गया है:

...
locked(
  cast, {button,Digit},
  #{code := Code, length := Length, buttons := Buttons} = Data) ->
...
    if
        NewButtons =:= Code -> % Correct
	    do_unlock(),
            {next_state, open, Data#{buttons := []},
             [{{timeout,open},10000,lock}]}; % Time in milliseconds
...

open({timeout,open}, lock, Data) ->
    do_lock(),
    {next_state,locked,Data};
open(cast, {button,_}, Data) ->
    {keep_state,Data};
...

विशिष्ट जेनेरिक टाइम-आउट बस उसी प्रकार हो सकता है, जिसे State Time-Outs को नए समय या infinity सेट करके पुनः प्रारंभ या रद्द किया infinity

इस विशेष मामले में हमें टाइमआउट को रद्द करने की आवश्यकता नहीं है, क्योंकि टाइमआउट इवेंट राज्य को open से locked बदलने का एकमात्र संभावित कारण है।

किसी टाइम-आउट को रद्द करने के साथ परेशान करने के बजाय, एक लेट टाइम-आउट ईवेंट को अनदेखा करके नियंत्रित किया जा सकता है यदि यह उस स्थिति में आता है जहां इसे देर से जाना जाता है।

3.18 एर्लैंग टाइमर

समय-बहिष्कार को संभालने का सबसे बहुमुखी तरीका एर्लांग टाइमर का उपयोग करना है; erlang:start_timer/3,4 देखें erlang:start_timer/3,4 अधिकांश टाइम-आउट कार्यों को gen_statem में टाइम-आउट सुविधाओं के साथ किया जा सकता है, लेकिन इसका एक उदाहरण जो ऐसा नहीं हो सकता है यदि आपको erlang:cancel_timer(Tref) से वापसी मूल्य की आवश्यकता होनी चाहिए erlang:cancel_timer(Tref) , erlang:cancel_timer(Tref) ; टाइमर का शेष समय।

यहां बताया गया है कि एर्लैंग टाइमर का उपयोग करके पिछले उदाहरण में राज्य टाइम-आउट को कैसे पूरा करें:

...
locked(
  cast, {button,Digit},
  #{code := Code, length := Length, buttons := Buttons} = Data) ->
...
    if
        NewButtons =:= Code -> % Correct
	    do_unlock(),
	    Tref =
                 erlang:start_timer(
                     10000, self(), lock), % Time in milliseconds
            {next_state, open, Data#{buttons := [], timer => Tref}};
...

open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) ->
    do_lock(),
    {next_state,locked,maps:remove(timer, Data)};
open(cast, {button,_}, Data) ->
    {keep_state,Data};
...

जब हम स्टेट locked में बदलते हैं तो मैप से timer हटाना सख्ती से आवश्यक नहीं है क्योंकि हम केवल अपडेटेड timer मैप वैल्यू के साथ स्टेट open में प्राप्त कर सकते हैं। लेकिन राज्य Data में पुराने मानों का न होना अच्छा हो सकता है!

यदि आपको किसी अन्य घटना के कारण टाइमर को रद्द करने की आवश्यकता है, तो आप erlang:cancel_timer(Tref) उपयोग कर सकते हैं। ध्यान दें कि इसके बाद एक टाइम-आउट संदेश नहीं आ सकता है, जब तक कि आपने इसे पहले स्थगित नहीं किया है (अगला अनुभाग देखें), इसलिए सुनिश्चित करें कि आप गलती से ऐसे संदेशों को स्थगित न करें। यह भी ध्यान दें कि आपके रद्द करने से ठीक पहले एक टाइम-आउट संदेश आ सकता है, इसलिए आपको erlang:cancel_timer(Tref) से वापसी मूल्य के आधार पर प्रक्रिया मेलबॉक्स से ऐसे संदेश को पढ़ना पड़ सकता है erlang:cancel_timer(Tref)

लेट टाइम-आउट को संभालने का एक और तरीका इसे रद्द नहीं करना हो सकता है, लेकिन इसे अनदेखा करने के लिए अगर यह उस स्थिति में आता है जहां इसे देर से जाना जाता है।

3.19 आयोजन स्थगित करना

यदि आप वर्तमान स्थिति में किसी विशेष घटना को अनदेखा करना चाहते हैं और इसे भविष्य की स्थिति में संभालना चाहते हैं, तो आप इस घटना को स्थगित कर सकते हैं। राज्य के बदल जाने के बाद एक स्थगित घटना वापस ले ली जाती है, OldState =/= NewState , OldState =/= NewState

postpone को राज्य संक्रमण State Transition Action postpone द्वारा आदेश दिया जाता है।

इस उदाहरण में, open राज्य में रहने के दौरान बटन की घटनाओं को अनदेखा करने के बजाय, हम उन्हें स्थगित कर सकते हैं और उन्हें कतारबद्ध और बाद में locked अवस्था में संभाला जा सकता है:

...
open(cast, {button,_}, Data) ->
    {keep_state,Data,[postpone]};
...

चूंकि एक स्थगित घटना केवल एक राज्य परिवर्तन के बाद वापस ले ली जाती है, आपको यह सोचना होगा कि राज्य डेटा आइटम कहां रखना है। आप इसे सर्वर Data या State ही रख सकते हैं, उदाहरण के लिए बूलियन मान रखने के लिए दो या अधिक समान handle_event_function , या Callback Mode handle_event_function साथ एक जटिल स्थिति (अनुभाग Complex State देखें) का उपयोग करके। यदि मूल्य में परिवर्तन से संभाले जाने वाले घटनाओं के सेट में परिवर्तन होता है, तो मूल्य को राज्य में रखा जाना चाहिए। अन्यथा केवल सर्वर डेटा में परिवर्तन के बाद कोई स्थगित घटना नहीं होगी।

यह महत्वपूर्ण नहीं है यदि आप घटनाओं को स्थगित नहीं करते हैं। लेकिन अगर आप बाद में कुछ घटनाओं को स्थगित करना शुरू करने का फैसला करते हैं, तो अलग-अलग राज्यों के नहीं होने का डिज़ाइन दोष जब उन्हें होना चाहिए, बग ढूंढना मुश्किल हो सकता है।

फजी राज्य आरेख

यह असामान्य नहीं है कि एक राज्य आरेख निर्दिष्ट नहीं करता है कि उन घटनाओं को कैसे संभालना है जो चित्र में किसी विशेष राज्य में चित्रित नहीं हैं। उम्मीद है कि यह एक संबंधित पाठ में या संदर्भ से वर्णित है।

संभावित क्रियाएं: ड्रॉप ईवेंट के रूप में अनदेखा करें (हो सकता है कि इसे लॉग इन करें) या किसी अन्य स्थिति में ईवेंट से निपटने के लिए इसे स्थगित कर दें।

चयनात्मक प्राप्त

Erlang के चयनात्मक प्राप्त कथन का उपयोग अक्सर सरल राज्य मशीन के उदाहरणों को सीधे Erlang कोड में वर्णित करने के लिए किया जाता है। निम्नलिखित पहला उदाहरण का एक संभावित कार्यान्वयन है:

-module(code_lock).
-define(NAME, code_lock_1).
-export([start_link/1,button/1]).

start_link(Code) ->
    spawn(
      fun () ->
	      true = register(?NAME, self()),
	      do_lock(),
	      locked(Code, length(Code), [])
      end).

button(Button) ->
    ?NAME ! {button,Button}.
locked(Code, Length, Buttons) ->
    receive
        {button,Button} ->
            NewButtons =
                if
                    length(Buttons) < Length ->
                        Buttons;
                    true ->
                        tl(Buttons)
                end ++ [Button],
            if
                NewButtons =:= Code -> % Correct
                    do_unlock(),
		    open(Code, Length);
                true -> % Incomplete | Incorrect
                    locked(Code, Length, NewButtons)
            end
    end.
open(Code, Length) ->
    receive
    after 10000 -> % Time in milliseconds
	    do_lock(),
	    locked(Code, Length, [])
    end.

do_lock() ->
    io:format("Locked~n", []).
do_unlock() ->
    io:format("Open~n", []).

इस मामले में चयनात्मक प्राप्त का अर्थ है कि locked राज्य में किसी भी घटना को स्थगित करने के लिए open होना।

एक चयनात्मक प्राप्त का उपयोग किसी भी gen_* व्यवहार से किसी भी gen_* व्यवहार के लिए नहीं किया जा सकता है, क्योंकि प्राप्त कथन gen_* इंजन के भीतर ही है। यह वहाँ होना चाहिए क्योंकि सभी sys संगत व्यवहारों को सिस्टम संदेशों का जवाब देना चाहिए और इसलिए ऐसा करें कि उनके इंजन में लूप प्राप्त हो, नॉन-सिस्टम संदेशों को कॉलबैक मॉड्यूल में पास करें।

State Transition Action postpone को चयनात्मक प्राप्त करने के लिए डिज़ाइन किया गया है। चयनात्मक रूप से किसी भी प्राप्त नहीं होने वाली घटनाओं को स्पष्ट रूप से स्थगित कर postpone , लेकिन postpone स्थिति संक्रमण कार्रवाई स्पष्ट रूप से एक प्राप्त घटना को स्थगित कर देती है।

दोनों तंत्रों में एक ही सैद्धांतिक समय और स्मृति जटिलता है, जबकि चयनात्मक प्राप्त भाषा निर्माण में छोटे निरंतर कारक हैं।

3.20 स्टेट एंट्री एक्शन

मान लें कि आपके पास एक राज्य मशीन विनिर्देश है जो राज्य प्रवेश क्रियाओं का उपयोग करता है। हालाँकि, आप सम्मिलित किए गए ईवेंट (अगले भाग में वर्णित) का उपयोग करके इसे कोड कर सकते हैं, खासकर यदि केवल एक या कुछ राज्यों को राज्य में प्रवेश की कार्रवाई मिली है, यह बिल्ट इन State Enter Calls सही उपयोग है।

आप अपने state_enter callback_mode/0 फ़ंक्शन से state_enter वाली एक सूची state_enter और gen_statem इंजन gen_statem साथ एक बार आपके स्टेट कॉलबैक पर कॉल करेगा (enter, OldState, ...) फिर आपको बस सभी राज्यों में इन इवेंट-जैसे कॉल को संभालने की आवश्यकता है।

...
init(Code) ->
    process_flag(trap_exit, true),
    Data = #{code => Code, length = length(Code)},
    {ok, locked, Data}.

callback_mode() ->
    [state_functions,state_enter].

locked(enter, _OldState, Data) ->
    do_lock(),
    {keep_state,Data#{buttons => []}};
locked(
  cast, {button,Digit},
  #{code := Code, length := Length, buttons := Buttons} = Data) ->
...
    if
        NewButtons =:= Code -> % Correct
            {next_state, open, Data};
...

open(enter, _OldState, _Data) ->
    do_unlock(),
    {keep_state_and_data,
     [{state_timeout,10000,lock}]}; % Time in milliseconds
open(state_timeout, lock, Data) ->
    {next_state, locked, Data};
...

आप उनमें से किसी एक को वापस करके राज्य में दर्ज कोड दोहरा सकते हैं {repeat_state, ...} , {repeat_state_and_data,_} या repeat_state_and_data अन्यथा उनके keep_state भाई-बहनों की तरह व्यवहार करते हैं । state_callback_result() संदर्भ मैनुअल में प्रकार देखें ।

3.21 सम्मिलित घटनाएँ

कभी-कभी अपने स्वयं के राज्य मशीन में घटनाओं को उत्पन्न करने में सक्षम होना फायदेमंद हो सकता है। इसके साथ किया जा सकता है State Transition Action {next_event,EventType,EventContent}

आप किसी भी मौजूदा की घटनाओं को उत्पन्न कर सकते हैं type , लेकिन internal प्रकार केवल कार्रवाई के माध्यम से उत्पन्न किया जा सकता है next_event । इसलिए, यह एक बाहरी स्रोत से नहीं आ सकता है, इसलिए आप निश्चित हो सकते हैं कि एक internal घटना आपके राज्य की मशीन से स्वयं के लिए एक घटना है।

इसके लिए एक उदाहरण आने वाले डेटा को प्री-प्रोसेस करना है, उदाहरण के लिए विखंडू को डिक्रिप्ट करना या लाइन ब्रेक तक वर्ण एकत्र करना।

शुद्धतावादियों का तर्क हो सकता है कि इसे एक अलग राज्य मशीन से तैयार किया जाना चाहिए जो मुख्य राज्य मशीन को पूर्व-संसाधित इवेंट भेजता है, लेकिन ओवरहेड को कम करने के लिए छोटे प्री-प्रोसेसिंग स्टेट मशीन को मुख्य राज्य मशीन के सामान्य राज्य ईवेंट हैंडलिंग में लागू किया जा सकता है कुछ राज्य डेटा चर का उपयोग करके जो फिर पूर्व-संसाधित घटनाओं को आंतरिक घटनाओं के रूप में मुख्य राज्य मशीन पर भेजता है। आंतरिक घटनाओं का उपयोग करना भी राज्य मशीनों को सिंक्रनाइज़ करना आसान बना सकता है।

इसका एक प्रकार एक के Complex State साथ प्रयोग करना है One Event Handler । राज्य तो उदाहरण के लिए एक टुपल के साथ मॉडलिंग की है {MainFSMState,SubFSMState}

इसका वर्णन करने के लिए हम एक उदाहरण बनाते हैं जहाँ बटन नीचे और ऊपर (प्रेस और रिलीज़) घटनाओं को उत्पन्न करते हैं, और लॉक नीचे की घटना के बाद ही एक अप इवेंट के लिए प्रतिक्रिया करता है।

...
-export(down/1, up/1).
...
down(Button) ->
    gen_statem:cast(?NAME, {down,Button}).

up(Button) ->
    gen_statem:cast(?NAME, {up,Button}).

...

locked(enter, _OldState, Data) ->
    do_lock(),
    {keep_state,Data#{remaining => Code, buf => []}};
locked(
  internal, {button,Digit},
  #{code := Code, length := Length, buttons := Buttons} = Data) ->
...
handle_common(cast, {down,Button}, Data) ->
    {keep_state, Data#{button := Button}};
handle_common(cast, {up,Button}, Data) ->
    case Data of
        #{button := Button} ->
            {keep_state,maps:remove(button, Data),
             [{next_event,internal,{button,Button}}]};
        #{} ->
            keep_state_and_data
    end;
...

open(internal, {button,_}, Data) ->
    {keep_state,Data,[postpone]};
...

यदि आप इस कार्यक्रम को अपने साथ शुरू करते हैं तो आप के साथ code_lock:start([17]) अनलॉक कर सकते हैं code_lock:down(17), code_lock:up(17).

3.22 उदाहरण पर दोबारा गौर किया

इस खंड में अधिकांश उल्लिखित संशोधनों के बाद उदाहरण शामिल है और कुछ और राज्य प्रवेश कॉल का उपयोग करते हैं, जो एक नए राज्य आरेख के हकदार हैं:

चित्र 3.2: कोड लॉक राज्य आरेख पुनरीक्षित

ध्यान दें कि यह राज्य आरेख निर्दिष्ट नहीं करता है कि राज्य में एक बटन ईवेंट को कैसे संभालना है open । तो, आपको कुछ साइड नोट्स में पढ़ने की जरूरत है, जो कि यहां है: कि अनिर्दिष्ट घटनाओं को स्थगित कर दिया जाएगा (कुछ बाद की स्थिति में नियंत्रित किया जाएगा)। इसके अलावा, राज्य आरेख यह नहीं दर्शाता है कि code_length/0 कॉल को प्रत्येक राज्य में नियंत्रित किया जाना चाहिए।

कॉलबैक मोड: state_functions

राज्य कार्यों का उपयोग करना:

-module(code_lock).
-behaviour(gen_statem).
-define(NAME, code_lock_2).

-export([start_link/1,stop/0]).
-export([down/1,up/1,code_length/0]).
-export([init/1,callback_mode/0,terminate/3]).
-export([locked/3,open/3]).

start_link(Code) ->
    gen_statem:start_link({local,?NAME}, ?MODULE, Code, []).
stop() ->
    gen_statem:stop(?NAME).

down(Digit) ->
    gen_statem:cast(?NAME, {down,Digit}).
up(Digit) ->
    gen_statem:cast(?NAME, {up,Digit}).
code_length() ->
    gen_statem:call(?NAME, code_length).
init(Code) ->
    process_flag(trap_exit, true),
    Data = #{code => Code, length => length(Code), buttons => []},
    {ok, locked, Data}.

callback_mode() ->
    [state_functions,state_enter].

-define(HANDLE_COMMON,
    ?FUNCTION_NAME(T, C, D) -> handle_common(T, C, D)).
%%
handle_common(cast, {down,Button}, Data) ->
    {keep_state, Data#{button => Button}};
handle_common(cast, {up,Button}, Data) ->
    case Data of
        #{button := Button} ->
            {keep_state, maps:remove(button, Data),
             [{next_event,internal,{button,Button}}]};
        #{} ->
            keep_state_and_data
    end;
handle_common({call,From}, code_length, #{code := Code}) ->
    {keep_state_and_data,
     [{reply,From,length(Code)}]}.
locked(enter, _OldState, Data) ->
    do_lock(),
    {keep_state, Data#{buttons := []}};
locked(state_timeout, button, Data) ->
    {keep_state, Data#{buttons := []}};
locked(
  internal, {button,Digit},
  #{code := Code, length := Length, buttons := Buttons} = Data) ->
    NewButtons =
        if
            length(Buttons) < Length ->
                Buttons;
            true ->
                tl(Buttons)
        end ++ [Button],
    if
        NewButtons =:= Code -> % Correct
	    do_unlock(),
            {next_state, open, Data};
	true -> % Incomplete | Incorrect
            {keep_state, Data#{buttons := NewButtons},
             [{state_timeout,30000,button}]} % Time in milliseconds
    end;
?HANDLE_COMMON.
open(enter, _OldState, _Data) ->
    do_unlock(),
    {keep_state_and_data,
     [{state_timeout,10000,lock}]}; % Time in milliseconds
open(state_timeout, lock, Data) ->
    {next_state, locked, Data};
open(internal, {button,_}, _) ->
    {keep_state_and_data, [postpone]};
?HANDLE_COMMON.

do_lock() ->
    io:format("Locked~n", []).
do_unlock() ->
    io:format("Open~n", []).

terminate(_Reason, State, _Data) ->
    State =/= locked andalso do_lock(),
    ok.

कॉलबैक मोड: handle_event_function

यह अनुभाग वर्णन करता है कि एक handle_event/4 फ़ंक्शन का उपयोग करने के लिए उदाहरण में क्या बदलना है । ईवेंट के आधार पर पहले ब्रांच के लिए पहले से उपयोग किया जाने वाला तरीका काम नहीं करता है क्योंकि यहाँ स्टेट कॉल के कारण अच्छी तरह से काम करता है, इसलिए यह उदाहरण पहले ब्रांच पर निर्भर करता है:

-export([handle_event/4]).
callback_mode() ->
    [handle_event_function,state_enter].
%%
%% State: locked
handle_event(enter, _OldState, locked, Data) ->
    do_lock(),
    {keep_state, Data#{buttons := []}};
handle_event(state_timeout, button, locked, Data) ->
    {keep_state, Data#{buttons := []}};
handle_event(
  internal, {button,Digit}, locked,
  #{code := Code, length := Length, buttons := Buttons} = Data) ->
    NewButtons =
        if
            length(Buttons) < Length ->
                Buttons;
            true ->
                tl(Buttons)
        end ++ [Button],
    if
        NewButtons =:= Code -> % Correct
	    do_unlock(),
            {next_state, open, Data};
	true -> % Incomplete | Incorrect
            {keep_state, Data#{buttons := NewButtons},
             [{state_timeout,30000,button}]} % Time in milliseconds
    end;
%%
%% State: open
handle_event(enter, _OldState, open, _Data) ->
    do_unlock(),
    {keep_state_and_data,
     [{state_timeout,10000,lock}]}; % Time in milliseconds
handle_event(state_timeout, lock, open, Data) ->
    {next_state, locked, Data};
handle_event(internal, {button,_}, open, _) ->
    {keep_state_and_data,[postpone]};
%% Common events
handle_event(cast, {down,Button}, _State, Data) ->
    {keep_state, Data#{button => Button}};
handle_event(cast, {up,Button}, _State, Data) ->
    case Data of
        #{button := Button} ->
            {keep_state, maps:remove(button, Data),
             [{next_event,internal,{button,Button}},
              {state_timeout,30000,button}]}; % Time in milliseconds
        #{} ->
            keep_state_and_data
    end;
handle_event({call,From}, code_length, _State, #{length := Length}) ->
    {keep_state_and_data,
     [{reply,From,Length}]}.

ध्यान दें कि open राज्य से राज्य तक के बटन को स्थगित locked करना एक कोड लॉक करने के लिए एक अजीब बात की तरह लगता है, लेकिन यह कम से कम घटना को स्थगित करता है।

3.23 राज्य को फ़िल्टर करें

इस अध्याय में अब तक के उदाहरण सर्वर त्रुटि लॉग में पूर्ण आंतरिक स्थिति को प्रिंट करते हैं, उदाहरण के लिए, जब एक्जिट सिग्नल द्वारा मारा जाता है या आंतरिक त्रुटि के कारण। इस स्थिति में दोनों कोड लॉक कोड होते हैं और कौन से अंक अनलॉक करने के लिए रहते हैं।

इस राज्य डेटा को संवेदनशील माना जा सकता है, और शायद कुछ अप्रत्याशित घटना के कारण आप त्रुटि लॉग में नहीं चाहते हैं।

राज्य को फ़िल्टर करने का एक अन्य कारण यह हो सकता है कि राज्य प्रिंट करने के लिए बहुत बड़ा है, क्योंकि यह त्रुटिहीन विवरण के साथ त्रुटि लॉग को भरता है।

इससे बचने के लिए, आप उस आंतरिक स्थिति को प्रारूपित कर सकते हैं जो त्रुटि लॉग में मिलती है और sys:get_status/1,2 फ़ंक्शन को लागू करने से वापस आती है Module:format_status/2 , उदाहरण के लिए:

...
-export([init/1,terminate/3,format_status/2]).
...

format_status(Opt, [_PDict,State,Data]) ->
    StateData =
	{State,
	 maps:filter(
	   fun (code, _) -> false;
	       (_, _) -> true
	   end,
	   Data)},
    case Opt of
	terminate ->
	    StateData;
	normal ->
	    [{data,[{"State",StateData}]}]
    end.

किसी Module:format_status/2 फ़ंक्शन को लागू करना अनिवार्य नहीं है। यदि आप नहीं करते हैं, तो एक डिफ़ॉल्ट कार्यान्वयन का उपयोग किया जाता है जो इस उदाहरण के रूप में करता है Data , जो शब्द को छानने के बिना कार्य करता है, अर्थात्, StateData = {State,Data} इस उदाहरण में संवेदनशील जानकारी है।

3.24 जटिल अवस्था

कॉलबैक मोड handle_event_function एक गैर-परमाणु स्थिति का उपयोग करने में सक्षम बनाता है जैसा कि अनुभाग में वर्णित है Callback Modes , उदाहरण के लिए, एक ट्यूपल की तरह एक जटिल राज्य शब्द।

इसका उपयोग करने का एक कारण यह है कि जब आपके पास एक ऐसा राज्य आइटम होता है जिसे परिवर्तित होने पर रद्द करना चाहिए State Time-Out , या एक जो स्थगित घटनाओं के साथ संयोजन में घटना से निपटने को प्रभावित करता है। हम बाद के लिए जाएंगे और एक विन्यास योग्य लॉक बटन (यह प्रश्न में राज्य आइटम है) को पेश करके पिछले उदाहरण को जटिल करेंगे, जो open राज्य में तुरंत दरवाजे को लॉक करता है, और set_lock_button/1 लॉक बटन को सेट करने के लिए एक एपीआई फ़ंक्शन ।

मान लीजिए कि set_lock_button दरवाजा खुला रहने के दौरान हम कॉल करते हैं, और हमने पहले ही एक बटन ईवेंट को स्थगित कर दिया है जो नया लॉक बटन था:

1> code_lock:start_link([a,b,c], x).
{ok,<0.666.0>}
2> code_lock:button(a).
ok
3> code_lock:button(b).
ok
4> code_lock:button(c).
ok
Open
5> code_lock:button(y).
ok
6> code_lock:set_lock_button(y).
x
% What should happen here?  Immediate lock or nothing?

हम कह सकते हैं कि बटन को बहुत पहले दबाया गया था, इसलिए इसे लॉक बटन के रूप में मान्यता नहीं दी जानी चाहिए। या हम लॉक बटन को राज्य का हिस्सा बना सकते हैं, इसलिए जब हम तब लॉक स्टेट में लॉक बटन को बदलते हैं, तो परिवर्तन एक राज्य परिवर्तन बन जाता है और सभी स्थगित घटनाओं को वापस ले लिया जाता है, इसलिए लॉक तुरंत लॉक हो जाता है!

हम उस स्थिति को परिभाषित करते हैं {StateName,LockButton} , StateName जैसा कि पहले था और LockButton वर्तमान लॉक बटन है:

-module(code_lock).
-behaviour(gen_statem).
-define(NAME, code_lock_3).

-export([start_link/2,stop/0]).
-export([button/1,set_lock_button/1]).
-export([init/1,callback_mode/0,terminate/3]).
-export([handle_event/4]).

start_link(Code, LockButton) ->
    gen_statem:start_link(
        {local,?NAME}, ?MODULE, {Code,LockButton}, []).
stop() ->
    gen_statem:stop(?NAME).

button(Button) ->
    gen_statem:cast(?NAME, {button,Button}).
set_lock_button(LockButton) ->
    gen_statem:call(?NAME, {set_lock_button,LockButton}).
init({Code,LockButton}) ->
    process_flag(trap_exit, true),
    Data = #{code => Code, length => length(Code), buttons => []},
    {ok, {locked,LockButton}, Data}.

callback_mode() ->
    [handle_event_function,state_enter].

%% State: locked
handle_event(enter, _OldState, {locked,_}, Data) ->
    do_lock(),
    {keep_state, Data#{buttons := []}};
handle_event(state_timeout, button, {locked,_}, Data) ->
    {keep_state, Data#{buttons := []}};
handle_event(
  cast, {button,Digit}, {locked,LockButton},
  #{code := Code, length := Length, buttons := Buttons} = Data) ->
    NewButtons =
        if
            length(Buttons) < Length ->
                Buttons;
            true ->
                tl(Buttons)
        end ++ [Button],
    if
        NewButtons =:= Code -> % Correct
	    do_unlock(),
            {next_state, {open,LockButton}, Data};
	true -> % Incomplete | Incorrect
            {keep_state, Data#{buttons := NewButtons},
             [{state_timeout,30000,button}]} % Time in milliseconds
    end;
%%
%% State: open
handle_event(enter, _OldState, {open,_}, _Data) ->
    do_unlock(),
    {keep_state_and_data,
     [{state_timeout,10000,lock}]}; % Time in milliseconds
handle_event(state_timeout, lock, {open,_}, Data) ->
    {next_state, locked, Data};
handle_event(cast, {button,LockButton}, {open,LockButton}, Data) ->
    {next_state, {locked,LockButton}, Data};
handle_event(cast, {button,_}, {open,_}, Data) ->
    {keep_state_and_data,[postpone]};
%%
%% Common events
handle_event(
  {call,From}, {set_lock_button,NewLockButton},
  {StateName,OldLockButton}, Data) ->
    {next_state, {StateName,NewLockButton}, Data,
     [{reply,From,OldLockButton}]}.
do_lock() ->
    io:format("Locked~n", []).
do_unlock() ->
    io:format("Open~n", []).

terminate(_Reason, State, _Data) ->
    State =/= locked andalso do_lock(),
    ok.

3.25 हाइबरनेशन

यदि आपके पास एक नोड में कई सर्वर हैं और उनके जीवनकाल में कुछ राज्य हैं, जिसमें सर्वर को थोड़ी देर के लिए निष्क्रिय करने की उम्मीद की जा सकती है, और इन सभी सर्वरों की ढेर स्मृति की मात्रा एक समस्या है, तो मेमोरी पदचिह्न एक सर्वर के माध्यम से इसे हाइबरनेट करके नकल की जा सकती है proc_lib:hibernate/3

ध्यान दें

बल्कि एक प्रक्रिया को हाइबरनेट करना महंगा है; देखते हैं erlang:hibernate/3 । यह ऐसा कुछ नहीं है जिसे आप हर घटना के बाद करना चाहते हैं।

हम इस उदाहरण में {open,_} राज्य में हाइबरनेट कर सकते हैं , क्योंकि उस स्थिति में सामान्य रूप से क्या होता है कि थोड़ी देर के बाद राज्य का समय एक संक्रमण को ट्रिगर करता है {locked,_} :

...
%%
%% State: open
handle_event(enter, _OldState, {open,_}, _Data) ->
    do_unlock(),
    {keep_state_and_data,
     [{state_timeout,10000,lock}, % Time in milliseconds
      hibernate]};
...

राज्य में hibernate प्रवेश करते समय अंतिम पंक्ति पर कार्रवाई सूची में परमाणु {open,_} ही परिवर्तन है। यदि कोई भी घटना {open,_}, राज्य में आती है , तो हम रिहैबनेट करने की जहमत नहीं उठाते हैं, इसलिए सर्वर किसी भी घटना के बाद जागता रहता है।

यह बदलने के लिए कि हमें hibernate और स्थानों पर कार्रवाई करने की आवश्यकता होगी । उदाहरण के लिए, राज्य-स्वतंत्र set_lock_button ऑपरेशन का उपयोग करना होगा hibernate लेकिन केवल {open,_} राज्य में, जो कोड को अव्यवस्थित करेगा।

एक और नहीं असामान्य परिदृश्य Event Time-Out निष्क्रियता के एक निश्चित समय के बाद हाइबरनेशन को ट्रिगर करने के लिए उपयोग करना है। वहाँ भी एक सर्वर शुरू विकल्प है {hibernate_after, Timeout} के लिए start/3,4 या start_link/3,4 कि स्वचालित रूप से सर्वर हाइबरनेट के लिए इस्तेमाल किया जा सकता है।

यह विशेष रूप से सर्वर संभवतया हाइबरनेटिंग के लिए हीप मेमोरी का उपयोग नहीं करता है। हाइबरनेशन से कुछ भी हासिल करने के लिए, आपके सर्वर को कॉलबैक निष्पादन के दौरान गैर-महत्वहीन कचरा उत्पन्न करना होगा, जिसके लिए यह उदाहरण सर्वर खराब उदाहरण के रूप में काम कर सकता है।

Original text