Φτάνοντας πιο μακριά: το Peta::FFI για γραφικά#

Τα κεφάλαια των εργαλειοθηκών κάλυψαν αυτό που το CPAN ήδη τυλίγει. Αυτό το κεφάλαιο, και ο υπόλοιπος οδηγός, πηγαίνει πιο μακριά: προσεγγίζει μια βιβλιοθήκη γραφικών σε C που δεν έχει καθόλου σύνδεση με το CPAN, χρησιμοποιώντας το εγγενές FFI της pperl. Αυτή είναι μια δυνατότητα αποκλειστική της pperl. Η σύνδεση μιας βιβλιοθήκης C με τον παραδοσιακό τρόπο σημαίνει τη συγγραφή ενός shim σε C, τη μεταγλώττισή του και την αποστολή μιας εργαλειοαλυσίδας κατασκευής μαζί με τον κώδικά σας· η pperl καλεί τη βιβλιοθήκη C απευθείας, τον ίδιο μηχανισμό που σας δίνει ήδη το Math::GMP χωρίς καμία μεταγλωττισμένη σύνδεση να κατασκευαστεί.

Αυτό δεν είναι το FFI::Platypus. Το Peta::FFI είναι ενσωματωμένο στον χρόνο εκτέλεσης, και οι επιμελημένες συνδέσεις του είναι εγγενής Rust, όχι δηλώσεις από την πλευρά της Perl. Η διάκριση έχει σημασία για τα γραφικά, γιατί μια πραγματική σύνδεση γραφικών χρειάζεται να διαχειρίζεται αντικείμενα δεσμευμένα από τη C (μια επιφάνεια, ένα context) σε πολλές κλήσεις, και το μοντέλο της pperl το χειρίζεται με τον τρόπο που το κάνει η ίδια η βιβλιοθήκη C.

Η έγκυρη αναφορά για το στρώμα FFI είναι η σελίδα Auto-FFI· αυτό το κεφάλαιο την εφαρμόζει ειδικά στα γραφικά.

Τα τρία επίπεδα#

Το Peta::FFI κατανοείται καλύτερα ως τρία επίπεδα, από το ακατέργαστο ως το επιμελημένο.

Επίπεδο 1, ακατέργαστες κλήσεις. Ονομάζετε μια βιβλιοθήκη, μια συνάρτηση και μια συμβολοσειρά υπογραφής τύπων, και η pperl δρομολογεί την κλήση μέσω του libffi. Τίποτα δεν παράγεται εκ των προτέρων· οποιαδήποτε υπογραφή μπορείτε να γράψετε λειτουργεί.

use Peta::FFI qw(dlopen call dlclose);

my $lib = dlopen("libm.so.6");
my $r = call($lib, "sqrt", "(d)d", 2.0);     # sqrt(2) = 1.4142...
dlclose($lib);

Η υπογραφή "(args)ret" χρησιμοποιεί ένα γράμμα ανά τύπο: v void, i int, l long, L unsigned long ή size_t, d double, f float, p μια συμβολοσειρά C ως είσοδο, και P έναν μεταβλητό ενταμιευτή εξόδου. Αυτό το σύνολο καλύπτει μια εκπληκτική ποσότητα ενός API της C: οτιδήποτε του οποίου τα ορίσματα και η επιστροφή είναι βαθμωτά, συμβολοσειρές ή ένας ενταμιευτής bytes.

Επίπεδο 2, ένα στρώμα περιγραφής δεδομένων. Πολλές συναρτήσεις C παίρνουν ή επιστρέφουν structs, όχι βαθμωτά, και οι ακατέργαστοι κωδικοί τύπων δεν μπορούν να περιγράψουν τη διάταξη ενός struct. Η προγραμματισμένη απάντηση είναι ένα δηλωτικό στρώμα όπου περιγράφετε τα πεδία μιας εγγραφής μία φορά και αφήνετε το FFI να τη μεταφέρει. Αυτή είναι η αναδυόμενη μέση οδός· δεν είναι ακόμη ένα άρθρωμα που αποστέλλεται, και αυτός ο οδηγός είναι ρητός σε κάθε σημείο για το πού θα το χρειαζόταν μια σύνδεση.

Επίπεδο 3, επιμελημένες συνδέσεις. Το ανώτατο επίπεδο είναι μια χειρόγραφη εγγενής σύνδεση: ένα άρθρωμα Rust που ανοίγει τη βιβλιοθήκη μία φορά, επιλύει κάθε σύμβολο που χρειάζεται, και παρουσιάζει ένα καθαρό API της Perl, διαχειριζόμενο τα αντικείμενα που έχουν δεσμευτεί από τη C πίσω από τιμές της Perl. Το Math::GMP είναι ακριβώς αυτό: use Math::GMP και παίρνετε ακεραίους αυθαίρετης ακρίβειας με υπερφόρτωση τελεστών, και πουθενά δεν αγγίξατε το dlopen ή μια συμβολοσειρά υπογραφής. Το επόμενο κεφάλαιο παίρνει μία από αυτές που η pperl ήδη αποστέλλει για μια βιβλιοθήκη γραφικών, το Cairo, και δείχνει πώς λειτουργεί.

Τι μπορούν και τι δεν μπορούν να προσεγγίσουν οι ακατέργαστες κλήσεις#

Η διαχωριστική γραμμή για τα γραφικά είναι το struct. Ένα μεγάλο μέρος ενός API γραφικών σε C είναι προσεγγίσιμο στο Επίπεδο 1 αυτή τη στιγμή, γιατί είναι βαθμωτά και αδιαφανείς χειριστές:

  • Οι συναρτήσεις που παίρνουν και επιστρέφουν αριθμούς (ορίζουν ένα πλάτος γραμμής, μια ακτίνα, μια συνιστώσα χρώματος) αντιστοιχίζονται απευθείας στα i, d, f και τα συναφή.

  • Οι συναρτήσεις που παίρνουν μια συμβολοσειρά C (φορτώνουν μια γραμματοσειρά με το όνομά της, ανοίγουν ένα αρχείο) χρησιμοποιούν το p.

  • Οι συναρτήσεις που γεμίζουν έναν ενταμιευτή που παρέχει ο καλών (διαβάζουν pixel σε μνήμη που σας ανήκει) χρησιμοποιούν το P.

  • Ένας αδιαφανής χειριστής που επιστρέφεται από τη βιβλιοθήκη (ένας δείκτης επιφάνειας, ένας δείκτης context) είναι απλώς μια διεύθυνση. Η pperl αναπαριστά ήδη έναν χειριστή βιβλιοθήκης ως ακέραιο, και το επιμελημένο πρότυπο στο επόμενο κεφάλαιο αποθηκεύει αυτούς τους χειριστές αντικειμένων με τον ίδιο τρόπο: ως ακέραιο μέσα σε μια blessed αναφορά της Perl.

Πού σταματούν οι ακατέργαστες κλήσεις:

  • Structs που περνιούνται ή επιστρέφονται κατά τιμή. Μια συνάρτηση που παίρνει ένα Color { r, g, b, a } κατά τιμή, ή επιστρέφει ένα Rectangle, δεν μπορεί να περιγραφεί από τους κωδικούς βαθμωτών τύπων. Αυτό είναι το κενό του Επιπέδου 2.

  • Callbacks. Μια βιβλιοθήκη που καλεί πίσω τον κώδικά σας (ένας βρόχος συμβάντων που επικαλείται έναν χειριστή, ένα σήμα σχεδίασης) χρειάζεται έναν δείκτη συνάρτησης C που ξαναμπαίνει στην Perl. Αυτό είναι πέρα από το ακατέργαστο στρώμα.

  • Unions. Ένας τύπος συμβάντος που είναι ένα union από πολλά σχήματα struct (η κλασική εγγραφή συμβάντος παραθύρων) συνδυάζει και τα δύο παραπάνω προβλήματα.

Αυτά δεν είναι μόνιμα τείχη· είναι το σύνορο προς το οποίο μεγαλώνουν τα στρώματα του FFI. Η αξία αυτού του οδηγού είναι ότι σας λέει ακριβώς σε ποια πλευρά της γραμμής πέφτει μια δεδομένη εργασία, ώστε ούτε να αποφεύγετε αυτό που λειτουργεί ούτε να υπόσχεστε αυτό που δεν λειτουργεί.

Γιατί μια επιμελημένη σύνδεση, όχι ακατέργαστες κλήσεις, για πραγματική δουλειά#

Θα μπορούσατε να οδηγήσετε μια βιβλιοθήκη γραφικών εξ ολοκλήρου μέσω κλήσεων call του Επιπέδου 1, και για ένα γρήγορο πείραμα αυτό είναι εντάξει. Για οτιδήποτε πραγματικό, μια επιμελημένη σύνδεση του Επιπέδου 3 είναι η σωστή μονάδα, για τρεις λόγους.

Πρώτον, η διάρκεια ζωής του αντικειμένου. Ένα context γραφικών δεσμεύεται από τη βιβλιοθήκη και πρέπει να αποδεσμευτεί ακριβώς μία φορά. Μια επιμελημένη σύνδεση δένει αυτή τη διάρκεια ζωής στο DESTROY μιας τιμής της Perl, οπότε το αντικείμενο C αποδεσμεύεται όταν το αντικείμενο της Perl εξαφανίζεται, χωρίς χειροκίνητη λογιστική. Οι ακατέργαστες κλήσεις το αφήνουν εξ ολοκλήρου σε εσάς.

Η πρώτης τάξεως εργονομία της Perl είναι ο δεύτερος λόγος: το $surface->fill διαβάζεται καλύτερα και είναι δυσκολότερο να το κάνει κανείς λάθος από ένα call με χειρόγραφη υπογραφή σε κάθε σημείο χρήσης.

Τρίτον, η υπογραφή γράφεται μία φορά. Σε μια επιμελημένη σύνδεση οι συμβολοσειρές "(ppd)i" ζουν σε ένα άρθρωμα Rust, ελεγμένες μία φορά· κάθε καλών απλώς χρησιμοποιεί τη μέθοδο. Η διασπορά ακατέργαστων υπογραφών μέσα στον κώδικα της εφαρμογής είναι ο τρόπος με τον οποίο προκύπτει μια αναντιστοιχία που διαφθείρει τη στοίβα.

Η επίδειξη του Cairo κάνει και τα τρία απτά με τη σύνδεση Cairo που αποστέλλει η pperl, μια πραγματική βιβλιοθήκη vector της οποίας το API είναι σχεδόν εξ ολοκλήρου βαθμωτά και ένας αδιαφανής χειριστής, που είναι ακριβώς το σχήμα που προσεγγίζεται καθαρά σήμερα.

Το σχήμα αυτών που ακολουθούν#

Το επόμενο κεφάλαιο κατασκευάζει μια επιμελημένη σύνδεση Cairo με τον τρόπο που κατασκευάζεται το Math::GMP, ώστε να δείτε μια βιβλιοθήκη γραφικών να σηκώνεται στην pperl από άκρη σε άκρη. Έπειτα τα δύο ολοκληρωτικά έργα χρησιμοποιούν τη συντηρούμενη στοίβα εργαλειοθηκών από τον άξονα B, αγκυρωμένη στο Prima, για να φτιάξουν μια εφαρμογή σχεδίασης και ένα 2D παιχνίδι που μπορείτε να εκτελέσετε, με το νήμα του FFI υφασμένο εκεί όπου κερδίζει τη θέση του και ειλικρινά επισημασμένο εκεί όπου αρχίζει το σύνορο. Το τελευταίο κεφάλαιο σχεδιάζει τον χάρτη αυτών που απομένουν.