Bygg Real Time Web Applications Med Adobe Cirrus

Å bygge sanntidsnettet spill og applikasjoner kan være utfordrende. Denne opplæringen vil vise deg hvordan du kobler til flash-klienter med Cirrus, og presenterer deg for noen viktige teknikker.

La oss se på det endelige resultatet vi skal jobbe for. Klikk på startknappen i SWF ovenfor for å opprette en "send" -versjon av programmet. Åpne denne opplæringen igjen i et annet nettleservindu, kopier det nærmeste vinduet fra det første vinduet til tekstboksen, og klikk deretter Start for å opprette en mottakerversjon av programmet.

I mottakerversjonen ser du to roterende nåler: en rød, en blå. Den blå nålen roterer på egen hånd, med en konstant hastighet på 90 ° / sekund. Den røde nålen roterer for å matche vinkelen som sendes ut av "send" -versjonen.

(Hvis den røde nålen virker spesielt laggy, må du prøve å flytte nettleservinduene slik at du kan se begge SWF-ene samtidig. Flash Player kjører EnterFrame-hendelser til en mye lavere hastighet når nettleservinduet er i bakgrunnen, slik at "send" -vinduet overfører den nye vinkelen mye sjeldnere.)


Trinn 1: Komme i gang

Første ting først: Du trenger en Cirrus 'utvikler nøkkel', som kan hentes på Adobe Labs-siden. Dette er en tekststreng som unikt er tildelt deg ved registrering. Du vil bruke dette i alle programmene du skriver for å få tilgang til tjenesten, så det kan være best å definere det som en konstant i en av dine AS-filer, slik:

 offentlig statisk const CIRRUS_KEY: String = "";

Vær oppmerksom på at det er hver utvikler eller utviklingsgruppe som trenger sin egen nøkkel, ikke hver bruker av hvilke applikasjoner du oppretter.


Trinn 2: Koble til Cirrus-tjenesten

Vi begynner med å opprette en nettverkstilkobling ved hjelp av en forekomst av (du gjettet det) NetConnection klasse. Dette oppnås ved å ringe koble() metode med den tidligere nevnte nøkkelen, og nettadressen til en Cirrus 'rendezvous' -server. Siden Cirrus bruker en lukket protokoll, er det bare en slik server; adressen er rtmfp: //p2p.rtmfp.net

 offentlig klasse Cirrus offentlig statisk const CIRRUS_KEY: String = ""private static var netConnection: NetConnection; offentlig statisk funksjon Init (nøkkel: String): void if (netConnection! = null) return; netConnection = ny NetConnection (); prøv netConnection.connect (" rtmfp: //p2p.rtmfp .net ", nøkkel); fangst (e: Feil) 

Siden ingenting skjer umiddelbart i nettverkskommunikasjon, netConnection objektet vil gi deg beskjed om hva det gjør ved å skyte hendelser, spesielt NetStatusEvent. Den viktige informasjonen blir holdt i kode egenskapen til arrangementet info gjenstand.

 privat funksjon OnStatus (e: NetStatusEvent): void switch (e.info.code) tilfelle "NetConnection.Connect.Success": break; // Forbindelsesforsøket lyktes. tilfelle "NetConnection.Connect.Closed": break; // Tilkoblingen ble stengt med hell. tilfelle "NetConnection.Connect.Failed": break; // Tilkoblingsforsøket mislyktes. 

Et mislykket tilkoblingsforsøk skyldes vanligvis at enkelte porter blokkeres av en brannmur. Hvis dette er tilfelle, har du ikke annet valg enn å rapportere feilen til brukeren, da de ikke vil koble til noen før situasjonen endres. Suksess, derimot, belønner deg med din egen nearID. Dette er en strengegenskap for NetConnection-objektet som representerer den aktuelle NetConnection, på den aktuelle Flash Player, på den aktuelle datamaskinen. Ingen andre NetConnection-objekter i verden vil ha samme nærID.

NærID er som ditt eget personlige telefonnummer - folk som vil snakke med deg, må vite det. Det motsatte er også sant: du vil ikke kunne koble til noen andre uten å vite deres nærtid. Når du leverer noen andre med ditt nærmesteID, vil de bruke det som en Farid: farID er IDen til klienten du prøver å koble til. Hvis noen andre gir deg sin nærtid, kan du bruke den som en farID til å koble til dem. Skjønner?

Så alt vi trenger å gjøre er å koble til en klient og be dem om deres nærtid, og så ... å vent. Hvordan finner vi ut deres nærmesteID (å bruke som vår farID) hvis vi ikke er koblet til hverandre i utgangspunktet? Svaret, som du vil bli overrasket over å høre, er at det er umulig. Du trenger en slags tredjepartstjeneste for å bytte over ids over. Eksempler ville være:

  • Bygg et serverprogram for å fungere som en "lobby"
  • E-post, eller instant messaging, din nærID til noen andre
  • Koker noe opp med NetGroups, som vi kan se på i en fremtidig opplæring

Trinn 3: Bruk av strømmer

Nettverksforbindelsen er rent konseptuell og hjelper oss ikke mye etter at forbindelsen er opprettet. Å faktisk overføre data fra den ene enden av forbindelsen til en annen bruker vi NetStream objekter. Hvis en nettverksforbindelse kan tenkes som å bygge en jernbane mellom to byer, er et NetStream et posttåg som bærer faktiske meldinger nedover banen.

NetStreams er enveis. Når de er opprettet, fungerer de som enten en utgiver (sender informasjon) eller en abonnent (mottar informasjon). Hvis du vil at en enkelt klient skal sende og motta informasjon over en tilkobling, vil du derfor trenge to NetStreams i hver klient. Når en gang er opprettet, kan NetStream gjøre fancy ting som lyd og video, men i denne opplæringen vil vi holde fast med enkle data.

Hvis, og bare hvis vi mottar en NetStatusEvent fra NetConnection med en kode på NetConnection.Connect.Success, Vi kan opprette et NetStream-objekt for den forbindelsen. For en utgiver må du først konstruere strømmen ved hjelp av en referanse til netConnection-objektet vi nettopp har opprettet, og den spesielle forhåndsdefinerte verdien. For det andre, ring publisere() på strømmen og gi den et navn. Navnet kan være alt du liker, det er bare der for en abonnent å skille mellom flere strømmer som kommer fra samme klient.

 var ns: NetStream = ny NetStream (netConnection, NetStream.DIRECT_CONNECTIONS); ns.publish (navn, null);

For å opprette en abonnent, passerer du igjen netConnection-objektet til konstruktøren, men denne gangen sender du også farID til klienten du vil koble til. For det andre, ring spille() med navnet på strømmen som tilsvarer navnet på den andre klientens publiseringsstrøm. For å si det på en annen måte, hvis du publiserer en strøm med navnet "Test", må abonnenten bruke navnet "Test" for å koble til det.

 var ns: NetStream = ny NetStream (netConnection, farID); ns.play (navn);

Legg merke til hvordan vi trengte en farID for abonnenten, og ikke utgiveren. Vi kan lage så mange publiseringsstrømmer som vi liker, og alt de skal gjøre er å sitte der og vente på en tilkobling. Abonnenter, derimot, trenger å vite nøyaktig hvilken datamaskin i verden de skal abonnere på.


Trinn 4: Overføring av data

Når en publiseringsstrøm er satt opp, kan den brukes til å sende data. Nettstrømmen Sende Metoden tar to argumenter: Et "Handler" -navn, og et parameter med variabel lengde. Du kan passere ethvert objekt du liker som en av disse parameterne, inkludert grunnleggende typer som string, int og Nummer. Komplekse objekter blir automatisk serialisert - det vil si at de har alle sine egenskaper registrert på sendingssiden og deretter gjenopprettet på mottakssiden. Arrays og ByteArrays kopi bare bra også.

Håndteringsnavnet korresponderer direkte med navnet på en funksjon som til slutt blir kalt på mottaksiden. Variabelparameterlisten korresponderer direkte med argumentene mottakerfunksjonen vil bli kalt med. Så hvis en samtale er laget som:

 var jeg: int = 42; netStream.send ("Test", "Er det noen der?", i);

Mottakeren må ha en metode med samme navn og en tilsvarende signatur:

 offentlig funksjon Test (melding: String, num: int): void spor (melding + num); 

På hvilket objekt bør denne mottaksmetoden defineres? Ethvert objekt du liker. NetStream-forekomsten har en eiendom som kalles klient som kan akseptere ethvert objekt du tilordner det til. Det er objektet som Flash Player vil se etter en metode for det tilsvarende sendingsnavnet. Hvis det ikke finnes noen metode med det navnet, eller hvis antall parametere er feil, eller hvis noen av argumenttypene ikke kan konverteres til parametertypen, AsyncErrorEvent vil bli sparket for avsenderen.


Trinn 5: Trekker alt sammen

La oss konsolidere de tingene vi har lært så langt ved å sette alt inn i en slags rammeverk. Her er hva vi vil inkludere:

  • Koble til Cirrus-tjenesten
  • Opprette publisering og abonnementstrømmer
  • Sende og motta data
  • Registrering og rapportering feil

For å motta data trenger vi en måte å overføre et objekt til rammen som har medlemsfunksjoner som kan kalles som svar på de tilsvarende sendte samtalene. I stedet for en vilkårlig objektparameter, skal jeg kode et bestemt grensesnitt. Jeg kommer også til å sette inn i grensesnittet noen tilbakekallinger for de ulike feilhendelsene Cirrus kan sende ut - på den måten kan jeg ikke bare ignorere dem.

 pakke import flash.events.ErrorEvent; importer flash.events.NetStatusEvent; importer flash.net.NetStream; offentlige grensesnitt ICirrus funksjon onPeerConnect (abonnent: NetStream): boolsk; funksjon onStatus (e: NetStatusEvent): void; funksjon onError (e: ErrorEvent): void; 

Jeg vil at min Cirrus-klasse skal være så lett å bruke som mulig, så jeg vil gjemme de grunnleggende detaljene for strømmer og tilkoblinger fra brukeren. I stedet har jeg en klasse som fungerer som enten en avsender eller mottaker, og som automatisk kobler Flash Player til Cirrus-tjenesten hvis en annen forekomst ikke har gjort det allerede.

 pakke import flash.events.AsyncErrorEvent; importer flash.events.ErrorEvent; importer flash.events.EventDispatcher; importer flash.events.IOErrorEvent; importer flash.events.NetStatusEvent; importere flash.events.SecurityErrorEvent; importer flash.net.NetConnection; importer flash.net.NetStream; offentlig klasse Cirrus private static var netConnection: NetConnection; offentlig funksjon få nc (): NetConnection return netConnection;  // Koble til cirrus-tjenesten, eller hvis netConnection-objektet ikke er null // antar at vi allerede er tilkoblet statisk statisk funksjon Init (nøkkel: String): void if (netConnection! = Null) return; netConnection = ny NetConnection (); prøv netConnection.connect ("rtmfp: //p2p.rtmfp.net", nøkkel);  fangst (e: Feil) // Kan ikke koble av sikkerhetsgrunner, ingen poeng forsøker.  offentlig funksjon Cirrus (nøkkel: String, iCirrus: ICirrus) Init (nøkkel); this.iCirrus = iCirrus; netConnection.addEventListener (AsyncErrorEvent.ASYNC_ERROR, OnError); netConnection.addEventListener (IOErrorEvent.IO_ERROR, OnError); netConnection.addEventListener (SecurityErrorEvent.SECURITY_ERROR, OnError) netConnection.addEventListener (NetStatusEvent.NET_STATUS, OnStatus); hvis (netConnection.connected) netConnection.dispatchEvent (ny NetStatusEvent (NetStatusEvent.NET_STATUS, falsk, falsk, kode: "NetConnection.Connect.Success")));  privat var iCirrus: ICirrus; offentlige var ns: NetStream = null; 

Vi har en metode for å gjøre Cirrus-objektet til en utgiver, og en annen for å gjøre det til en avsender:

 offentlig funksjon Publiser (navn: String, wrapSendStream: NetStream = null): void if (wrapSendStream! = null) ns = wrapSendStream; ellers prøv ns = ny NetStream (netConnection, NetStream.DIRECT_CONNECTIONS);  fangst (e: Feil) return;  ns.addEventListener (NetStatusEvent.NET_STATUS, OnStatus); ns.addEventListener (AsyncErrorEvent.ASYNC_ERROR, OnError); ns.addEventListener (IOErrorEvent.IO_ERROR, OnError); ns.client = iCirrus; ns.publish (navn, null);  offentlig funksjon Spill (farId: String, navn: String): void try ns = new NetStream (netConnection, farId);  fangst (e: Feil) return;  ns.addEventListener (NetStatusEvent.NET_STATUS, OnStatus); ns.addEventListener (AsyncErrorEvent.ASYNC_ERROR, OnError); ns.addEventListener (IOErrorEvent.IO_ERROR, OnError); ns.client = iCirrus; prøv ns.play.apply (ns, [name]);  fangst (e: feil) 

Til slutt må vi passere hendelsene til grensesnittet vi opprettet:

 privat funksjon OnError (e: ErrorEvent): void iCirrus.onError (e);  privat funksjon OnStatus (e: NetStatusEvent): void iCirrus.onStatus (e); 

Trinn 6: Opprette et testprogram

Vurder følgende scenario som involverer to Flash-applikasjoner. Den første appen har en nål som stadig roterer rundt i en sirkel (som en hånd på et klokke ansikt). På hver ramme av appen roteres hånden litt lenger, og også den nye vinkelen sendes over internett til mottaksprogrammet. Mottakelsesappen har en nål, hvis vinkel er satt utelukkende fra den siste meldingen mottatt fra sendeappen. Her er et spørsmål: Skal begge nåler (nålen for sendeappen og nålen for mottaksprogrammet) alltid peke til samme posisjon? Hvis du svarte ja, anbefaler jeg at du leser videre.

La oss bygge den og se. Vi tegner en enkel nål som en linje som eminerer fra opprinnelsen (koordinater (0,0)). På denne måten, når vi setter formenes rotasjonsegenskaper, vil nålen alltid rotere som om den ene enden er løst, og vi kan også enkelt plassere formen ved hvor dreiepunktet skal være:

 privat funksjon CreateNeedle (x: tall, y: tall, lengde: tall, kol: uint, alfa: tall): form var form: form = nytt form (); shape.graphics.lineStyle (2, kol, alfa); shape.graphics.moveTo (0, 0); shape.graphics.lineTo (0, -length); // tegne peker oppover shape.graphics.lineStyle (); shape.x = x; shape.y = y; returnere form; 

Det er ubeleilig å sette opp to datamaskiner ved siden av hverandre, så på mottakeren bruker vi faktisk to nåler. Den første (røde nålen) vil fungere som i beskrivelsen ovenfor, og setter sin vinkel utelukkende fra den siste mottatte meldingen; Den andre (blå nålen) vil få sin startposisjon fra den første rotasjonsmeldingen mottatt, men roter deretter automatisk over tid uten videre meldinger, akkurat som sendingsnålen gjør. På denne måten kan vi se eventuelle uoverensstemmelser mellom hvor nålen skal være og hvor de mottatte rotasjonsmeldingene sier at den skal være, alt ved å starte begge appene og bare vise mottakerappen.

 privat var først: boolsk = sant; // Kalt av mottaker nettstrøm når en melding sendes offentlig funksjon Data (verdi: Nummer): void shapeNeedleB.rotation = value; hvis (først) shapeNeedleA.rotation = value; først = false;  privat var datoLast: Dato = null; privat funksjon OnEnterFrame (e: Event): void if (dateLast == null) dateLast = new Date (); // Trene ut tiden som er gått siden siste ramme. var dateNow: Date = new Date (); var s: Nummer = (datoNå.tid - datoLast.tid) / 1000; dateLast = dateNow; // Nåle A er alltid avansert på hver ramme. // Men hvis det er en mottaksstrøm vedlagt, // også overføre verdien av rotasjonen. shapeNeedleA.rotation + = 360 * (s / 4); hvis (cirrus.ns.peerStreams.length! = 0) cirrus.ns.send ("Data", shapeNeedleA.rotation); 

Vi har et tekstfelt på appen som lar brukeren legge inn en farID for å koble til. Hvis appen startes uten å legge inn en farID, vil den sette seg opp som utgiver. Det dekker stort sett å lage appen du ser øverst på siden. Hvis du åpner to nettlesere, kan du kopiere ID fra ett vindu til det andre, og angi en app for å abonnere på den andre. Det vil faktisk fungere for alle to datamaskiner som er koblet til Internett - men du må ha noen form for kopiering over abonnentens nærmesteID.


Trinn 7: Å sette en nøkkel i verkene

Hvis du kjører både avsender og mottaker på samme datamaskin, har rotasjonsinformasjonen for nålen ikke langt å reise. Faktisk må de datapakker som sendes ut fra avsenderen ikke engang berøre det lokale nettverket i det hele tatt fordi de er bestemt for samme maskin. I virkelige forhold må dataene gjøre mange humle fra datamaskin til datamaskin, og med hvert hopp introdusert øker sannsynligheten for problemer.

Latency er et slikt problem. Jo lenger dataene fysisk må reise, desto lengre vil det ta å ankomme. For en datamaskin basert i London, vil dataene ta mindre tid å komme fra New York (en fjerdedel av veien rundt om i verden) enn fra Sydney (halvveis rundt om i verden). Nettverksbelastning er også et problem. Når en enhet på Internett opererer på metningspunktet og blir bedt om å overføre enda en pakke, kan den ikke gjøre noe annet enn å kaste bort det. Programvare som bruker Internett må da oppdage den tapte pakken og spør avsenderen for en annen kopi, som alle legger til lag i systemet. Avhengig av hver ende av forbindelsens plassering i verden, tidspunkt på dagen og tilgjengelig båndbredde vil kvaliteten på forbindelsen variere mye.

Så hvordan håper du å teste for alle disse forskjellige scenariene? Det eneste praktiske svaret er ikke å gå ut og prøve å finne alle disse forskjellige forholdene, men å gjenopprette en gitt tilstand etter behov. Dette kan oppnås ved å bruke noe som kalles en "WAN emulator".

En WAN (Wide Area Network) emulator er programvare som forstyrrer nettverkstrafikken som kjører til og fra maskinen den kjører på, slik at man prøver å gjenopprette forskjellige nettverksforhold. For eksempel, ved å bare kaste ut nettverkspakker som er overført fra en maskin, kan det etterligne pakketapet som kan oppstå på et tidspunkt i den virkelige overføringen av dataene. Ved å forsinke pakker med noe beløp før de sendes ut av nettverkskortet, kan det simulere ulike nivåer av latens.

Det finnes ulike WAN emulatorer, for ulike plattformer (Windows, Mac, Linux), alle lisensiert på ulike måter. For resten av denne artikkelen skal jeg bruke Softperfect Connection Emulator for Windows av to grunner: det er enkelt å bruke, og det har en gratis prøveversjon.

(Forfatteren og Tuts + er på ingen måte knyttet til det nevnte produktet. Bruk på egen risiko.)

Når WAN Emulator er installert og kjører, kan du enkelt teste det ved å laste ned en slags strøm (for eksempel Internett-radio eller streaming video) og gradvis øke mengden av tap av tap. Uunngåelig vil avspillingen stoppe når pakketapet når noen kritisk verdi som avhenger av båndbredden og strømmenes størrelse.

Oh, og vær oppmerksom på følgende punkter:

  • Hvis både sende- og mottakappene er på samme datamaskin, fungerer forbindelsen fint, men WAN-emulatoren vil ikke kunne påvirke pakkene som sendes mellom dem. Dette skyldes at (på Windows i det minste) pakker som er bestemt for samme datamaskin, ikke sendes til nettverksenheten. En avsender og mottaker på samme lokale nettverk fungerer fint, men - pluss du kan kopiere nærID til en tekstfil, slik at du ikke trenger å skrive den ned.
  • I disse dager, når et brosjyrevindu er minimert, reduserer nettleseren kunstig reduseringen av SWF. Hold nettleservinduet synlig på skjermen for å få konsistente resultater.

SoftPerfect-emulator som viser pakktap

I normal tilstand ser du de røde og blå nålene peker til stort sett den samme posisjonen, kanskje med den røde nålen flimrer iblant som den faller bak, og plutselig fanger opp igjen. Nå, hvis du setter WAN-emulatoren til 2% pakktap, vil du se at effekten blir mye mer uttalt: omtrent hvert sekund eller så vil du se det samme flimmer. Dette er bokstavelig talt hva som skjer når pakken som bærer rotasjonsinformasjonen går tapt: den røde nålen sitter bare og venter på neste pakke. Tenk deg hvordan det ville se ut hvis appen ikke overførte nålrotasjonen, men stillingen til en annen spiller i et multiplayer-spill - tegnet ville stamme hver gang det flyttet til en ny posisjon.

Ved ugunstige forhold kan du forvente (og derfor bør designe for) opptil 10% pakktap. Prøv dette med WAN Emulator, og du kan få et glimt av et andre fenomen. Det er tydelig at stammepåvirkningen er mer uttalt - men hvis du ser nøye ut, merker du at når nålen faller langt bak, går den ikke helt tilbake til den riktige posisjonen, men må fort "vind" fremover igjen.

I spilleksemplet er dette uønsket av to grunner. Først vil det se merkelig ut for å se et tegn, ikke bare stamming, men deretter positivt zoome inn mot den tilsiktede posisjonen. For det andre, hvis alt vi ønsker å se er et spillerkarakter i sin nåværende stilling, bryr vi oss ikke om alle disse mellommennsposisjonene: Vi vil bare ha den nyeste posisjonen når pakken går tapt og deretter sendes igjen. All informasjon bortsett fra den nyeste er bortkastet tid og båndbredde.


SoftPerfect-emulator som viser latens

Sett pakketapet tilbake til null, og vi vil se på latens. Det er usannsynlig at i virkelige forhold vil du aldri bli bedre enn ca 30 ms latens, så sett din WAN Emulator for det. Når du aktiverer emuleringen, vil du legge merke til at nålen faller tilbake ganske enkelt, siden hvert sluttpunkt omkonfigurerer seg til den nye nettverkshastigheten. Deretter vil nålen komme opp igjen til det er konsekvent noen avstand bak der den skal være. Faktisk de to nålene vil se stein solid: bare litt bortsett fra hverandre mens de roterer. Ved å angi forskjellige mengder latens, 30ms, 60ms, 90ms, kan du praktisk talt kontrollere hvor langt fra hverandre nålene er.

Bilde dataspillet igjen med spilleren tegnet alltid litt avstand bak der de burde være. Hver gang du tar sikte på spilleren og tar et skudd, kommer du til å savne, for hver gang du retter opp skottet, ser du på hvor spilleren pleide å være, og ikke hvor de er nå. Jo verre latensen, jo mer åpenbart problemet. Spillere med dårlige internettforbindelser kan for alle formål være uskadelige!


Trinn 8: Pålitelighet

Det er ikke mange raske løsninger i livet, så det er en glede å forholde seg til det følgende. Da vi så på pakktap, så vi hvordan nålen skulle merket seg fremover ettersom det hadde gått opp til den tilsiktede rotasjonen etter tap av informasjon. Årsaken til dette er at bak kulissene hadde hver pakke sendt et serienummer knyttet til det som angav ordren.

Med andre ord, hvis avsenderen skulle sende ut 4 pakker ...

A, B, C, D

Og hvis en, la oss si "B" er tapt i overføring slik at mottakeren får ...

A, C, D

... mottaksstrømmen kan passere "A" umiddelbart til appen, men må da informere avsenderen om denne manglende pakken, vente på at den skal mottas igjen, og deretter sende "overført kopi av B ',' C ',' D'. Fordelen med dette systemet er at meldinger alltid mottas i den rekkefølgen de ble sendt, og at eventuelle manglende opplysninger fylles ut automatisk. Ulempen er at tapet av en enkelt pakke medfører relativt store forsinkelser i overføringen.

I dataspillet som diskuteres (der vi oppdaterer spillerens karakter i sanntid), til tross for ikke å faktisk miste informasjon, er det bedre å bare vente på at neste pakke kommer sammen enn å ta seg tid til å fortelle avsenderen og vent på re-overføring. Etter at pakken 'B' kommer, vil den allerede bli erstattet av pakker 'C' og 'D', og dataene den inneholder vil bli foreldet.

Fra Flash Player 10.1 ble en eiendom lagt til NetStream-klassen for å kontrollere bare denne typen oppførsel. Det brukes slik:

 offentlig funksjon SetRealtime (ns: NetStream): void ns.dataReliable = false; ns.bufferTime = 0; 

Spesielt er det dataReliable eiendom som ble lagt til, men av tekniske grunner bør den alltid brukes i forbindelse med innstilling av bufferTime eiendom til null. Hvis du endrer koden for å angi sendings- og mottaksstrømmene på denne måten og kjøre en annen test på tap av tap, vil du se at viklingseffekten forsvinner.


Trinn 9: Interpolering

Det er en start, men det er fortsatt en veldig jittery nål. Problemet er at posisjonen til mottakingsnålen er helt til nådig av de mottatte meldingene. Ved selv 10% pakktap er det store flertallet av informasjonen fortsatt mottatt, men fordi grafisk avhenger appen så mye på en jevn og vanlig meldingstrøm, oppstår en liten uoverensstemmelse umiddelbart.

Vi vet hvordan rotasjonen skal se ut; hvorfor ikke bare fylle ut den manglende informasjonen til tapet over sprekken? Vi starter med en klasse som følgende som har to metoder, en for oppdatering med den nyeste rotasjonen, en for å lese av den aktuelle rotasjonen:

 offentlig klasse Msg offentlig funksjon Skriv (verdi: Nummer, dato: Dato): void  offentlig funksjon Les (): Nummer 

Nå har prosessen blitt "avkoblet". Hver ramme vi kan ringe til Lese() Metode og oppdater formens rotasjon. Når og når nye meldinger kommer inn, kan vi ringe til Skrive() Metode for å oppdatere klassen med den nyeste informasjonen. Vi justerer også appen slik at den mottar ikke bare rotasjonen, men tiden rotasjonen ble sendt.

Prosessen med å fylle ut manglende verdier fra kjente, kalles interpole. Interpolering er et stort emne som tar mange former, så vi skal håndtere et undergruppe som heter Lineær interpolering, eller 'Lerping'. Programatisk ser det slik ut:

 offentlig funksjon Lerp (a: tall, b: tall, x: tall): tall return a + ((b - a) * x); 

A og B er noen to verdier; X er vanligvis en verdi mellom null og en. Hvis X er null, returnerer metoden A. Hvis X er en, returnerer metoden B. For fraksjonelle verdier mellom null og en returnerer metoden verdier delvis mellom A og B - slik at en X-verdi på 0,25 returnerer en verdi 25% av veien fra A til B.

Med andre ord, hvis klokken 1:00 har O've kjørt 5 miles, og klokken 14:00 har jeg kjørt 60 miles, så klokken 13:30 har jeg kjørt Lerp (5, 60, 0,5) miles. Som det skjer kan jeg ha spratt opp, bremset ned og ventet i trafikken på ulike deler av reisen, men interpolasjonsfunksjonen kan ikke regne med det da det bare har to verdier å jobbe fra. Resultatet er derfor en lineær tilnærming og ikke et eksakt svar.

 // Hold 2 siste verdier for å interpolere fra. privat var valueA: Number = NaN; privat varverdiB: Nummer = NaN; // Og de tidsspor som verdiene refererer til. privat var secA: Number = NaN; privat var secB: Number = NaN; offentlig funksjon Skriv (verdi: Nummer, dato: Dato): tomrom var sekC: Nummer = date.time / 1000.0; // Hvis den nye verdien er rimelig fjern fra sist //, sett deretter a som b og b som ny verdi. hvis (isNaN (secB) || secC -secB> 0,1) valueA = valueB; secA = secB; verdiB = verdi; secB = secC;  offentlig funksjon Les (): Nummer hvis (isNaN (verdiA)) returverdiB; Var sekC: Nummer = Ny Dato (). Tid / 1000,0; var x: tall = (secC-secA) / (secB-secA); returner Lerp (verdiA, verdiB, x); 

Trinn 10: Så nær og likevel så langt

Hvis du implementerer koden ovenfor, vil du legge merke til at den nesten fungerer riktig, men ser ut til å ha en slags glitch - hver gang nålen gjør en rotasjon, ser det ut til å plutselig plukke tilbake i motsatt retning. Gjorde vi savner noe? Dokumentasjonen for rotasjonsegenskapen til Displayobject klassen avslører følgende:

Indikerer rotasjonen av DisplayObject-forekomsten, i grader, fra sin opprinnelige orientering. Verdier fra 0 til 180 representerer rotasjon med urviseren; verdier fra 0 til -180 representerer rotasjon mot urviseren. Verdier utenfor dette området legges til eller trekkes fra 360 for å oppnå en verdi innenfor området.

Det var naivt - vi antok en enkelt nummerlinje som vi kunne velge mellom to poeng og interpolere. I stedet snakker vi ikke med en linje, men med en sirkel av verdier. Hvis vi går forbi +180, brytes vi rundt igjen til -180. Det var derfor nålen oppførte seg merkelig. Vi trenger fortsatt å interpolere, men vi trenger en form for interpolering som kan vikle riktig rundt en sirkel.

Tenk deg å se på to separate bilder av noen som sykler. I det første bildet er pedalene plassert mot toppen av sykkelen; i det andre bildet er pedalene plassert mot sykkelens forside. Fra bare disse to bildene og uten ytterligere kunnskap er det ikke mulig å finne ut om rytteren går fremover eller bakover. Pedalene kunne ha avansert en fjerdedel av en sirkel fremover, eller tre fjerdedeler av en sirkel bakover. Som det skjer, i appen vi har bygget, er nålene alltid "pedal" fremover, men vi vil gjerne kode for det generelle tilfellet.

Den vanlige måten å løse dette på er å anta at den korteste avstanden rundt sirkelen er riktig retning, og håper også at oppdateringer kommer raskt nok til at det er forskjell på mindre enn en halv sirkel mellom hver oppdatering. Du har kanskje hatt erfaring med å spille et spill med flere spillere, hvor en annen spillers bil har rotert på en tilsynelatende umulig måte - det er grunnen til at.

 var min: tall = -180; var max: tall = +180; // Vi kan 'legge til' eller 'trekke' oss rundt sirkelen // gi to forskjellige mål av avstand var difAdd: Number = (b> a)? b-a: (max-a) + (b-min); var difSub: Nummer = (b < a)? a-b : (a-min) + (max-b);

Hvis 'difAdd' er mindre enn 'difSub', starter vi ved 'a' og legger til en lineær interpolering av mengden X. Hvis 'difSub' er mindre avstand, starter vi ved 'a' og trekker fra Det er en lineær interpolering av mengden X. Potensielt kan det gi en verdi som er utenfor mini- og max-området, så vi vil bruke en modulær aritmetikk for å få en verdi som er tilbake i rekkevidde igjen. Det fulle sett med beregninger ser slik ut:

 // En funksjon som gir et lignende resultat til% // mod-operatøren, men for flytverdien. offentlig funksjon Mod (val: Nummer, div: Nummer): Nummer retur (val - Math.floor (val / div) * div);  // Sikrer at verdier ut av min / maks rekkevidde // vikle riktig tilbake i området offentlig funksjon Sirkel (val: Nummer, Min: Nummer, Maks: Nummer): Nummer Retur Mod (Val - Min, )) + min;  // Utfører en sirkulær interpolering av A og B med faktor X, // innpakning i ekstremer min / maks offentlig funksjon CLerp (a: Nummer, b: Nummer, X: Nummer, Min: Nummer, Maks: Nummer): Nummer var difAdd: Number = (b> a)? b-a: (max-a) + (b-min); var difSub: Nummer = (b < a)? a-b : (a-min) + (max-b); return (difAdd < difSub)? Circle( a + (difAdd*x), min, max) : Circle( a - (difSub*x), min, max); 

Hvis du legger til dette i koden og testes på nytt, bør du finne mottakerens nål faktisk ser ganske jevn ut under en rekke nettverksforhold. Kilden koden knyttet til denne opplæringen har flere konstanter som kan endres for å kompilere med ulike kombinasjoner av funksjonene vi har diskutert.

Konklusjon

Vi begynte med å se på hvordan å lage en Cirrus-tilkobling og deretter sette opp NetStreams mellom klienter. Dette ble pakket inn i en resusable klasse som vi kunne teste med og utvide på. Vi opprettet et program og undersøkte ytelsen under ulike nettverksforhold ved hjelp av et verktøy, og så på teknikker for å forbedre opplevelsen for applikasjonsbrukeren. Endelig oppdaget vi at vi må bruke disse teknikkene med forsiktighet og med en forståelse av hvilke underliggende data appen representerer.

Jeg håper dette har gitt deg en grunnleggende grunnlag for å bygge sanntidsapplikasjoner, og at du nå føler at du er rustet til å møte problemene. Lykke til!