მოდულის დაწერა
ეს სახელმძღვანელო გაჩვენებთ, თუ როგორ უნდა დაწეროთ მარტივი სერვერის (და კლიენტის მხარის) მოდული Veloren-ისთვის. გთხოვთ გაითვალისწინოთ, რომ ორივე მოდულის API და დანამატის განვითარების პროცესი აქტიური განვითარების პროცესშია და ამჟამად არ გვაქვს სტაბილურობა გარანტიები API-ების, ხელსაწყოების შესახებ და ა.შ. თუ ეჭვი გეპარებათ, გთხოვთ, შეამოწმოთ ეს გვერდი, რათა იპოვოთ უახლესი ინფორმაცია.
ასევე მნიშვნელოვანია გვახსოვდეს, რომ მოდულის API ჯერ კიდევ ძალიან ახალია და ბევრი ფუნქციონალობა არ არის მხარდაჭერილი. იმედი გვაქვს რომ ამ სახელმძღვანელოს გამოქვეყნებით და ხალხის წახალისებით, რომ ექსპერიმენტები ჩაატარონ დანამატის API-ზე, რომლის შექმნაც შეიძლება დავიწყოთ კონსენსუსი სამომავლო მიმართულებაზე და სამომავლო ფუნქციებზე API-სთვის. თუ გაინტერესებთ დახმარება, დაგვიკავშირდით!
მაგალითი კოდი
შეგიძლიათ იპოვოთ ამ მაგალითის მოდულის კოდი აქ.
მოთხოვნები
-
საბაზისო Rust-ის ცოდნა
-
მარტივი Unix-ის მსგავსი ტერმინალების და ბრძანებების გამოყენების შესაძლებლობა
-
Veloren-ის განახლებული მაგალითი (თავსებადობის მიზნებისთვის სასურველია ადგილობრივი საცავი)
- შენიშვნა: გაქვთ პრობლემები? თავისუფლად იკითხეთ #learning Veloren Discord სერვერზე.*
მიმოხილვა
Veloren-ის დანამატები იწერება ვებ ასამბლეაში (აქ მოიხსენიება როგორც ‘WASM’), კოდის ფორმატი, რომელიც თავდაპირველად შექმნილია მაღალი წარმადობის, მეხსიერებისთვის უსაფრთხო ვებ შემსრულებელი ფაილები, მაგრამ ასევე იდეალურად შეეფერება სხვა მრავალ აპლიკაციებს. ეს გულისხმობს შემდეგ საკითხებს Veloren დანამატების შესახებ:
-
ისინი სავარჯიშოში არიან და ამიტომ უსაფრთხოა კლიენტის მხარეს გასაშვებად ავტომატურად
-
WASM-ს ჯერ არ აქვს კარგად განსაზღვრული ჰოსტი ABI, ამიტომ თამაშის ძრავთან კომუნიკაცია მოვლენებზეა ორიენტირებული
-
ისინი პორტატულია და იმუშავებენ ყველა არქიტექტურასა და პლატფორმაზე
-
(დაგეგმილი) დანამატები იმართება სერვერის მიერ და ეგზავნება კლიენტებს სერვერთან დაკავშირებისას, ამიტომ მოდულის გაწევრიანება ჩართულია სერვერი არის უწყვეტი პროცესი.
დაყენება
ჩვენ ვვარაუდობთ, რომ თქვენ იყენებთ Unix-ის მსგავს სისტემას, ან მსგავსი თვისებების მქონე გარემოს (როგორიცაა WSL ან Cygwin).
შენიშვნა: ამიერიდან, სადაც ხედავთ my_plugin, შეცვალეთ ეს თქვენი მოდულის სახელით.
პირველი, შექმენით ახალი სატვირთო პროექტი.
cargo new --lib my_plugin
დანამატებს აქვთ მრავალი შესვლის წერტილი (მაგ.: მოდულისგან ნივთების მოთხოვნის გზები), რომლებიც შეიძლება იყოს გამოძახებული თამაშის მიერ. რომ
დარწმუნდით, რომ ეს ყველაფერი სათანადოდ ხელმისაწვდომი იქნება თამაშისთვის, დაამატეთ შემდეგი Cargo.toml:
[lib]
crate-type = ["cdylib"]
იმის გამო, რომ Veloren-ის მოდულის API არასტაბილურია, ჩვენ დამოკიდებულნი ვიქნებით Veloren-ის git საცავზე. Cargo.toml-ში დაამატეთ
შემდეგ მისამართზე [dependencies]:
veloren-plugin-rt = { git = "https://gitlab.com/veloren/veloren.git" }
veloren-plugin-rt კრატი ახვევს მოდულის API-ს, მოვლენის დამმუშავებლის მიერ წარმოქმნილ მაკროებს და სხვა სასარგებლო ბიტების სერიას და
ნაწილებად იქცა ის, რასაც ჩვენ კოლოქიურად ვუწოდებთ „მოდულის გაშვებას“ (rt).
იმის გამო, რომ ჩვენ უნდა შევადგინოთ ჩვენი დანამატი WASM მოდულზე, ჯერ დარწმუნდით, რომ დაინსტალირებულია შესაბამისი ხელსაწყოების ჯაჭვი (და მუშაობს) შემდეგი მოქმედებებით:
cargo build --target wasm32-wasi
თუ მიიღებთ error[E0463]: can't find crate for 'core'-ს, შეგიძლიათ დააინსტალიროთ core-ის შესაბამისი ვერსია
შემდეგი rustup ბრძანება:
rustup target add wasm32-wasi
Veloren-ის კოდების ბაზა ამჟამად მოითხოვს Rust შემდგენელის ღამის ვერსიას (იმედი გვაქვს, რომ ეს ასე არ იქნება
მომავალი) და ასე რომ თქვენ ასევე გჭირდებათ. თუ უკვე არ იყენებთ ღამეს, შეგიძლიათ დააყენოთ კატალოგის სპეციფიკური გადაფარვა
ამისათვის შემდეგი ბრძანებით (დარწმუნდით, რომ თქვენ ხართ my-plugin დირექტორიაში, სანამ გაუშვით):
rustup override set nightly
მოდულის შეფუთვა
დანამატები შეფუთულია არაკომპრესირებულად (შეკუმშვა შეიძლება მოგვიანებით იყოს მხარდაჭერილი, მაგრამ ამჟამად არ არის)
tar არქივები გაფართოებით .plugin.tar. თითოეული არქივი შეიცავს:
-
ფაილი სახელით
plugin.toml, რომელიც განსაზღვრავს დანამატის მეტამონაცემებს -
და ნებისმიერი რაოდენობის WASM მოდული (პირობითად გაფართოებით
.wasm)
my_plugin.plugin.tar
|- plugin.toml
|- foo.wasm
`- bar.wasm
plugin.toml-ის ფორმატია TOML. საჭირო ველები საკმაოდ მარტივია, როგორც
ქვემოთ ნაჩვენები მაგალითი აჩვენებს (შეგიძლიათ გამოტოვოთ კომენტარები):
# The name of the plugin (lowercase, no spaces)
name = "my_plugin"
# A list of paths to WASM modules in the plugin (this can be used to group
# plugins together in a rudimentary way until we implement dependencies).
modules = []
# Plugins required by this plugin (currently unsupported, keep this empty)
dependencies = []
ჩვენ გვინდა დავიწყოთ ერთი მოდულის შექმნით. მოდით მივცეთ მას იგივე სახელი, როგორც ჩვენი პროექტი. დაიწყეთ მისი დამატებით
modules სიაში ასე:
modules = ["my_plugin.wasm"]
მოდულის შესაფუთად, ჩვენ შეგვიძლია დავაკოპიროთ შედგენილი WASM მოდული წინა ნაბიჯებიდან, რომელიც მდებარეობს
target/wasm32-wasi/debug/my_plugin.wasm თქვენს მიერ შექმნილ შეფუთვის დირექტორიაში ერთად
plugin.toml და შემდეგ გამოიყენეთ tar ბრძანება (ან თქვენი საყვარელი არქივის მენეჯერი), რომ შეფუთოთ ისინი. The
შემდეგი ბრძანება, რომელიც შესრულებულია შეფუთვის დირექტორიაში, კარგად უნდა მუშაობდეს:
tar -cvf ../my_plugin.plugin.tar *
შეიძლება დაგჭირდეთ ამ პროცესის ავტომატიზაცია სკრიპტით, რადგან ამას ხშირად გააკეთებთ. მარტივი shell სკრიპტი იქნებოდა სავარაუდოდ საკმარისია.
მომავალში ჩვენ გვსურს შევქმნათ ტვირთის ქვებრძანება, რომელიც ავტომატიზირებს ამ ნაბიჯს, მაგრამ ეს ჯერ არ გაკეთებულა.
ცნობისთვის, მე უბრალოდ ვიყენებ ჭურვის მარტივ სკრიპტს შემდეგი შინაარსით:
cargo build --target wasm32-wasi
cp target/wasm32-wasi/debug/my_plugin.wasm build_dir/.
cd build_dir
tar -cvf ../my_plugin.plugin.tar plugin.toml my_plugin.wasm
cd ..
მოდულის გაშვება
მოდულის გასაშვებად, უბრალოდ დააკოპირეთ ის plugins დირექტორიაში Veloren-ის აქტივების დირექტორიაში. მოდული იქნება
იგზავნება ქსელის მეშვეობით კლიენტებთან დამაკავშირებელთან, ამიტომ მნიშვნელოვანია მხოლოდ ის იყოს ხელმისაწვდომი სერვერისთვის (ან Voxygen-ისთვის, თუ
გსურთ მოდულის გაშვება singleplayer-ში).
ჩემს შემთხვევაში, ეს უბრალოდ გულისხმობს საბოლოო არქივის კოპირებას assets/plugins/my_plugin.tar-ში ჩემს ადგილობრივ საცავში
და თამაშის გაშვება.
როდესაც სერვერი იწყება (ან როდესაც ერთი მოთამაშე იწყება) თქვენ უნდა ნახოთ შემდეგი შეტყობინებები კონსოლში:
INFO veloren_common_state::plugin: Searching "/home/zesterer/projects/veloren/assets/plugins" for plugins...
INFO veloren_common_state::plugin: Loading plugin at "/home/zesterer/projects/veloren/assets/plugins/my_plugin.plugin.tar"
INFO veloren_common_state::plugin: Loaded plugin 'my_plugin' with 1 module(s)
თუ აქამდე მიაღწიეთ, გილოცავთ: თქვენ ოფიციალურად შექმენით თამაშის მოდული!
მოვლენების მართვა
ამ ეტაპზე, ღირს მოკლედ გადახედოთ მოდულის API-ს დოკუმენტაციას,
აქ. მიუხედავად იმისა, რომ ჩვენ დამოკიდებულნი ვართ veloren-plugin-rt, ანალოგიურად დასახელებულზე
veloren-plugin-api ყუთი ექსპორტირებულია მის მიერ ჩვენი მოხერხებულობისთვის. ჩვენ ახლა მზად ვართ დავწეროთ პირველი მოვლენის დამმუშავებელი
ჩვენი მოდული.
lib.rs-ში შეიყვანეთ შემდეგი:
#![allow(unused)]
fn main() {
use veloren_plugin_rt::{*, api::{*, event::*}};
#[event_handler]
pub fn on_load(load: PluginLoadEvent) {
emit_action(Action::Print(String::from("Hello, Veloren!")));
}
}
ამის ასახსნელად ღირს ცოტა დრო დახარჯოთ, განსაკუთრებით თუ არც ისე კარგად იცნობთ Rust-ს.
#![allow(unused)]
fn main() {
use veloren_plugin_rt::{*, api::{*, event::*}};
}
აქ ჩვენ იმპორტირებთ საჭირო მაკროებს, ტიპებსა და ფუნქციებს, რომლებიც გვჭირდება ჩვენი მოდულის დასაწერად.
#![allow(unused)]
fn main() {
#[event_handler]
pub fn on_load(load: PluginLoadEvent) { ... }
}
აქ ჩვენ ვაცხადებთ ახალ ფუნქციას, რომელიც იღებს a
PluginLoadEvent. ჩვენ ვიყენებთ
event_handler ატრიბუტი გაშვების დროისთვის რომ გვსურს გამოვიყენოთ ეს ფუნქცია მოვლენის დამმუშავებლად, რომელიც იქნება
გამოიძახება, როდესაც ხდება მითითებული ტიპის მოვლენა.
ამ შემთხვევაში, on_load ღონისძიება უბრალოდ გამოიძახება ერთხელ, როდესაც დანამატი პირველად ჩაიტვირთება სერვერის გაშვებისას.
#![allow(unused)]
fn main() {
emit_action(Action::Print(String::from("Hello, Veloren!")));
}
ჩვენ უკვე აღვნიშნეთ მოდულის შეყვანის მიღების გზა მოვლენის დამმუშავებლების საშუალებით. როგორ ვიმოქმედოთ ამ მოვლენებზე?
Actions მეშვეობით! Action არის ის, რაც გსურთ, რომ სერვერმა შეასრულოს და შეგიძლიათ გამოიყენოთ emit_action და
emit_actions ფუნქცია, რათა სერვერმა შეასრულოს ისინი.
თუ სერვერს ახლად შედგენილი მოდულით მართავთ, ახლა სერვერის კონსოლში უნდა ნახოთ შემდეგი:
INFO veloren_common_state::plugin::module: Hello, Veloren!
თუ თქვენ აწარმოებთ თამაშს ერთი მოთამაშით, ამას ორჯერ ნახავთ: ერთხელ შიდა სერვერისთვის და ერთხელ შიდა კლიენტი (მას შემდეგ, რაც ის მიიღებს დანამატს სერვერიდან).
ჩატის ბრძანებები
ჩვენ ვაპირებთ გავაფართოვოთ ჩვენი დანამატი ისე, რომ როდესაც ჩატში აკრიფებთ /ping, მოთამაშე მიიღებს პასუხს Pong!.
რატომ? კონკრეტული მიზეზი არ არის, მაგრამ ეს არის ჩატის ფუნქციონირების კარგი დემონსტრირება.
დაამატეთ შემდეგი lib.rs-ს:
#![allow(unused)]
fn main() {
#[event_handler]
pub fn on_command_ping(chat_cmd: ChatCommandEvent) -> Result<Vec<String>, String> {
Ok(vec![String::from("Pong!")])
}
}
ერთადერთი, რაც აქ ასახსნელია, არის რაღაც, რაც შეიძლება ცოტა მოულოდნელი იყოს წინა მაგალითის გათვალისწინებით: დაბრუნება ტიპი.
Event თვისების ყველა განმახორციელებელი (როგორიცაა PluginLoadEvent, ChatCommandEvent და ა.შ.) ასევე განსაზღვრავს პასუხს
რაც მას მოეთხოვება. PluginLoadEvent-ის შემთხვევაში, პასუხი არის უბრალოდ (), რის გამოც ჩვენ არ დაგვჭირდა
ცალსახად დააბრუნეთ არაფერი on_load მოვლენიდან. დაბრუნების ტიპი ChatCommandEvent-ისთვის განსხვავებულია, თუმცა: ის
მოელის ან ბრძანებაზე შეტყობინების პასუხების სიას, ან შეცდომის შეტყობინებას, თუ ბრძანების სინტაქსი არასწორია.
თუ თამაშს ახლად შედგენილი დანამატით აწარმოებთ და შემდეგ სამყაროში შედიხართ, უნდა შეგეძლოთ აკრიფოთ /ping
ესაუბრეთ და მიიღეთ Pong! პასუხად.
გლობალური სახელმწიფო
მოდულის API-ის ბოლო მახასიათებელია, რომელზეც უნდა ვისაუბროთ ამ გაკვეთილის დასრულებამდე: გლობალური სახელმწიფოს მართვა.
თუ თქვენ ცოტაზე მეტად იცნობთ Rust-ს, ეს შეიძლება საშინლად ჟღერდეს: მაგრამ იმის გათვალისწინებით, რომ მოვლენების დამმუშავებლები არიან თავად არიან „გლობალური“ (ანუ: ისინი ურთიერთობენ თამაშის მხოლოდ ერთ მაგალითთან, რომელმაც ჩატვირთა ის დანამატი, რომელიც ისინი არიან in), ასევე აზრი აქვს, რომ ნებისმიერი მონაცემი, რომლის შენახვაც გსურთ თქვენს დანამატში თამაშის მდგომარეობის შესახებ, ასევე უნდა იყოს გლობალური.
საბედნიეროდ, მოდულის API-ს აქვს ამის ფუნქცია!
თქვენი გლობალური სახელმწიფოს ტიპის დასადგენად, შეგიძლიათ დაამატოთ global_state ატრიბუტი მსგავსი ტიპის ზემოთ:
#![allow(unused)]
fn main() {
#[global_state]
#[derive(Default)]
struct State {
ping_count: u64,
}
}
ასევე მნიშვნელოვანია, რომ მან განახორციელოს Default თვისება: ეს საჭიროა, რადგან ჩვენ ჯერ არ ვუზრუნველყოფთ გზას
გლობალური მდგომარეობა ინიციალიზდება on_load მოვლენის დამმუშავებლის მეშვეობით.
მოვლენების დამმუშავებელში ამ გლობალურ მდგომარეობაზე წვდომისთვის, უბრალოდ დაამატეთ მეორე პარამეტრი მოვლენის დამმუშავებელს ასე:
#![allow(unused)]
fn main() {
#[event_handler]
pub fn on_command_ping(chat_cmd: ChatCommandEvent, state: &mut State) -> Result<Vec<String>, String> {
state.ping_count += 1;
Ok(vec![format!("Pong! The total number of pings so far is {}", state.ping_count)])
}
}
ახლა ნებისმიერ მოთამაშეს შეუძლია გამოიყენოს /ping და სერვერი ეტყვის, რამდენჯერ იქნა გამოყენებული ბრძანება სერვერის შემდეგ
დაიწყო!
მოდულის ზომის შემცირება
იხილეთ min-sized-rust ინფორმაციისთვის Rust-ის ზომის შემცირების შესახებ ბინარები.
მომავალი თემები
მომავალი შესაძლო თემები მოიცავს:
- მეტი ღონისძიების დამმუშავებლები
- ერთეულის ატრიბუტების შეცვლა
- მოდულის მუდმივი მდგომარეობა
- კონტროლი NPC AI
- მეტი მოდულის API ფუნქციები
- დანამატის სპეციფიკური აქტივები