]]>How are dtLinks created in NavMeshhttp://www.shnenglu.com/jinq0123/archive/2022/11/18/229525.html閲戝簡閲戝簡Fri, 18 Nov 2022 02:03:00 GMThttp://www.shnenglu.com/jinq0123/archive/2022/11/18/229525.htmlhttp://www.shnenglu.com/jinq0123/comments/229525.htmlhttp://www.shnenglu.com/jinq0123/archive/2022/11/18/229525.html#Feedback0http://www.shnenglu.com/jinq0123/comments/commentRss/229525.htmlhttp://www.shnenglu.com/jinq0123/services/trackbacks/229525.htmlHow are dtLinks created in NavMesh
(Jin Qing's Column, Nov., 2022)
dtLink
dtLink defines a link between 2 polygons.
It is an internal data structure.
A dtLink is owned by its source polygon.
How link is used
The links are used in findPath(), to search
all the neighbor polygons, from the current best polygon.
fields of dtLink
dtLink has 2 required fields:
neighbor: the neighbor polygon reference that is linked to.
edge: index of the polygon edge that owns this link.
It is used in getPortalPoints() and raycast().
If the link is a boundary link, which links to other tile, then it has more fields:
side: defines on which side the link is. 8 sides.
It is used in findPath() and raycast().
bmin: defines the minimum sub-edge area.
bmax: defines the maximum sub-edge area.
bmin and bmax are used in portal finding.
How are links created
Links are not saved to navmesh file. They are created on navmesh loading in addTile().
dtNavMesh::addTile() add a tile to the navmesh. A tile is a square block of the map.
The main job of dtNavMesh::addTile() is to create links inside this tile and between this tile and other tiles.
Actually a block indexed by (x, y) of constant size has many layers for multi-floor map.
A dtMeshTile is actually a layer with an index of (x, y, layer).
5 steps to create all links:
1. Connect internal links
connectIntLinks() iterates all the polygons in the tile and create links for them
by the help of neighbors information of the polygons.
2. Base off-mesh connections
baseOffMeshLinks() bases off-mesh connections to their starting polygons and connect connections inside the tile.
Off-mesh connection is between 2 points which are inside a tile or between 2 adjacent tiles.
For each off-mesh connection, there creates a specific polygon consisting of 2 vertices.
See DT_POLYTYPE_OFFMESH_CONNECTION.
baseOffMeshLinks() creates 2 links for each off-mesh connection:
From the off-mesh connection polygon to the source polygon
From the source polygon to the off-mesh connection polygon
The destinaton polygon of the off-mesh connection is skipped here.
3. Connect external off-mesn links within this tile
connectExtOffMeshLinks() with the source and target tile be this tile.
connectExtOffMeshLinks() searches off-mesh connections that are from this tile to the target tile
by checking the side direction of the connection.
It creates a link from off-mesh connection polygon to the target tile.
For bidirectional connection, it also creates a link from the target polygon to the off-mesh connection polygon.
So for each off-mesh connection of this tile, 3 or 4 links are created by baseOffMeshLinks() and connectExtOffMeshLinks(),
one on the source polygon, one on the destination polygon and 1/2 on the off-mesh connection polygon.
4. Connect with layers in current tile
For each layer other than this layer in this tile:
Create links from this layer to other layer
Create links from other layer to this layer
Create links of the off-mesh connection from this layer to other layer
Create links of the off-mesh connection from other layer to this layer
5. Connect with neighbour tiles
Check 9 neighbor tiles' layers, and create links just like previous step:
Create links from this layer to neighnor layer
Create links from neighnor layer to this layer
Create links of the off-mesh connection from this layer to neighnor layer
Create links of the off-mesh connection from neighnor layer to this layer
By now, for every off-mesh connection of all the loaded tiles, 3/4 links are created.
]]>Implementation of user data in cursive::Cursivehttp://www.shnenglu.com/jinq0123/archive/2022/10/02/229436.html閲戝簡閲戝簡Sun, 02 Oct 2022 08:38:00 GMThttp://www.shnenglu.com/jinq0123/archive/2022/10/02/229436.htmlhttp://www.shnenglu.com/jinq0123/comments/229436.htmlhttp://www.shnenglu.com/jinq0123/archive/2022/10/02/229436.html#Feedback0http://www.shnenglu.com/jinq0123/comments/commentRss/229436.htmlhttp://www.shnenglu.com/jinq0123/services/trackbacks/229436.htmlImplementation of user data in cursive::Cursive
letmut siv = cursive_core::Cursive::new();
// Start with a simple `Vec<i32>` as user data.
siv.set_user_data(vec![1i32, 2, 3]);
assert_eq!(siv.user_data::<Vec<i32>>(), Some(&mutvec![1i32, 2, 3]));
// Let's mutate the data a bit.
siv.with_user_data(|numbers: &mutVec<i32>| numbers.push(4));
// If mutable reference is not enough, we can take the data by value.let data: Vec<i32> = siv.take_user_data().unwrap();
assert_eq!(data, vec![1i32, 2, 3, 4]);
// At this point the user data was removed and is no longer available.assert_eq!(siv.user_data::<Vec<i32>>(), None);
When naming accessors within classes, non-trivial getters and queries, i.e., those that perform calculations, you should prepended get. All other getters have no prefix and setters have the set prefix.
class Object { public: int radius() const; void setRadius(int value);
]]>Rust sometimes needs manual type annotationhttp://www.shnenglu.com/jinq0123/archive/2022/04/29/229301.html閲戝簡閲戝簡Fri, 29 Apr 2022 02:14:00 GMThttp://www.shnenglu.com/jinq0123/archive/2022/04/29/229301.htmlhttp://www.shnenglu.com/jinq0123/comments/229301.htmlhttp://www.shnenglu.com/jinq0123/archive/2022/04/29/229301.html#Feedback0http://www.shnenglu.com/jinq0123/comments/commentRss/229301.htmlhttp://www.shnenglu.com/jinq0123/services/trackbacks/229301.htmlRust sometimes needs manual type annotation
Using navmesh generated by recastnavigation,
we found the agent will collide into the wall
when it is walking along the navmesh path.
After some inspection, we found the reason is that at the collision position,
the position on the path is simply too close to the wall, which is less than the agent radius.
If the agent radius is zero, the navmesh contour may inside the wall.
Displaying the raw contour and contour,
we can see the raw contour which is jigsaw shaped is right,
but the simplied contour uses a straight line to replace the jigsaw edge,
and causes the error.
The parameter "Max Edge Error" controls how the contour is simplified,
which default is 1.3, which means a vertex can deviate from the final contour
in the range of 1.3 times of cell size.
We hope the error is on only one side of the contour instead of on both sides.
In other words, the contour should within the raw contour.
walkContour() finds a raw contour which is a polygon.
simplifyContour() simplifies the raw contour resulting a simpler polygon.
The steps of simplifyContour():
Find lower-left and upper-right vertices of the contour to form the initial simplified polygon.
For each edge of the simplified polygon
For each point between 2 points of the edge
Find the point that is the most distant from the edge
If the distance is larger than the max error, add the point to the simplified polygon
Split too long edges.
We make these modifications:
The distance is signed
If the point is outside the walkable area, just make sure it is less than the max error
If the point is inside the walkable area
Add it to the simplified polygon to make sure the final contour is within the raw area
There are 2 kinds of contour:
Outline of a walkable area
Hole of a walkable area
We should decide the kind of the contour before simplifyContour().
If the start cell (x, y) is in the raw contour polygon, then it is an outline not a hole contour.
Because (x, y) may be one of the vetex of the polygon, we use (x + 0.5, y + 0.5) instead,
which is the center of the cell.
]]>How to work around rustup-init failurehttp://www.shnenglu.com/jinq0123/archive/2022/04/24/229295.html閲戝簡閲戝簡Sun, 24 Apr 2022 06:09:00 GMThttp://www.shnenglu.com/jinq0123/archive/2022/04/24/229295.htmlhttp://www.shnenglu.com/jinq0123/comments/229295.htmlhttp://www.shnenglu.com/jinq0123/archive/2022/04/24/229295.html#Feedback0http://www.shnenglu.com/jinq0123/comments/commentRss/229295.htmlhttp://www.shnenglu.com/jinq0123/services/trackbacks/229295.htmlHow to work around rustup-init failure
(Jin Qing's Column, Mar., 2022)
rustup-init.exe may fail if some anti-virus software is running with realtime protection.
The error message is like this after many retries:
error: could not rename component file from 'C:\.../rust/html' to 'C:\.../rust/html'
And it is not possible to stop the anti-virus for a corporate laptop.
There is a way to workaround this. Here are the steps:
Run rustup-init until it begins to retry renaming
Kill rustup-init when it is retrying
rustup will be installed before the failure
do not let rustup-init do the cleanup after the final failure
Run rustup update to install all.
Run rustup-init again to setup others.
rustup update will report that rustc was not found:
C:\Users\jinqing01>rustup update
info: syncing channel updates for 'stable-x86_64-pc-windows-msvc'
...
info: removing previous version of component 'rustc'
warning: during uninstall component rustc was not found
...
info: installing component 'rustc'
...
info: checking for self-updates
stable-x86_64-pc-windows-msvc updated - rustc 1.59.0 (9d1b2106e 2022-02-23) (from (rustc does not exist))
info: cleaning up downloads & tmp directories
Rerun rustup-init to setup the toolchain:
C:\Users\jinqing01>cargo --version
error: no override and no default toolchain set
C:\Users\jinqing01>d:
D:\>cd tool
D:\Tool>rustup-init.exe
Welcome to Rust!
This will download and install the official compiler for the Rust
programming language, and its package manager, Cargo.
...
Rust is installed now. Great!
Press the Enter key to continue.
D:\Tool>cargo --version
cargo 1.59.0 (49d8809dc 2022-02-10)
]]>Closure as the function parameterhttp://www.shnenglu.com/jinq0123/archive/2022/04/24/229294.html閲戝簡閲戝簡Sun, 24 Apr 2022 06:06:00 GMThttp://www.shnenglu.com/jinq0123/archive/2022/04/24/229294.htmlhttp://www.shnenglu.com/jinq0123/comments/229294.htmlhttp://www.shnenglu.com/jinq0123/archive/2022/04/24/229294.html#Feedback0http://www.shnenglu.com/jinq0123/comments/commentRss/229294.htmlhttp://www.shnenglu.com/jinq0123/services/trackbacks/229294.htmlClosure as the function parameter
(Jin Qing's Column, Mar., 2022)
It is best to let the function take a closure trait as the parameter instead of a function pointer.
fn foo(f: fn()) {
f()
}
fn main() {
foo(|| println!("hello"));
let a = 123;
foo(move || println!("{}", a))
}
compiles error:
error[E0308]: mismatched types
--> src/main.rs:9:9
|
9 | foo(move || println!("{}", a))
| ^^^^^^^^^^^^^^^^^^^^^^^^^ expected fn pointer, found closure
|
= note: expected fn pointer `fn()`
found closure `[closure@src/main.rs:9:9: 9:34]`
note: closures can only be coerced to `fn` types if they do not capture any variables
--> src/main.rs:9:32
|
9 | foo(move || println!("{}", a))
| ^ `a` captured here
For more information about this error, try `rustc --explain E0308`.
Function pointers implement all three of the closure traits (Fn, FnMut, and FnOnce), so you can always pass a function pointer as an argument for a function that expects a closure. It’s best to write functions using a generic type and one of the closure traits so your functions can accept either functions or closures.
]]>Deadlock detection must not run as tokio taskhttp://www.shnenglu.com/jinq0123/archive/2022/04/24/229293.html閲戝簡閲戝簡Sun, 24 Apr 2022 06:04:00 GMThttp://www.shnenglu.com/jinq0123/archive/2022/04/24/229293.htmlhttp://www.shnenglu.com/jinq0123/comments/229293.htmlhttp://www.shnenglu.com/jinq0123/archive/2022/04/24/229293.html#Feedback0http://www.shnenglu.com/jinq0123/comments/commentRss/229293.htmlhttp://www.shnenglu.com/jinq0123/services/trackbacks/229293.htmldeadlock detection must not run as tokio task
(Jin Qing's Column, Feb., 2022)
parking_lot has an experimental feature: deadlock_detection.
use std::thread;
use std::time::Duration;
use parking_lot::deadlock;
// Create a background thread which checks for deadlocks every 10s
thread::spawn(move || {
loop {
thread::sleep(Duration::from_secs(10));
let deadlocks = deadlock::check_deadlock();
if deadlocks.is_empty() {
continue;
}
println!("{} deadlocks detected", deadlocks.len());
for (i, threads) in deadlocks.iter().enumerate() {
println!("Deadlock #{}", i);
for t in threads {
println!("Thread Id {:#?}", t.thread_id());
println!("{:#?}", t.backtrace());
}
}
}
});
The output is like this:
1 deadlocks detected
Deadlock #0
Thread Id 16072
0: 0x7ff985cb659d - backtrace::backtrace::dbghelp::trace
at d:\Users\jinqing\.cargo\registry\src\github.com-1ecc6299db9ec823\backtrace-0.3.63\src\backtrace\dbghelp.rs:98
...
13: 0x7ff985ae92f3 - lock_api::rwlock::RwLock<parking_lot::raw_rwlock::RawRwLock,cgc::scene_template::SceneTemplate>::read<parking_lot::raw_rwlock::RawRwLock,cgc::scene_template::SceneTemplate>
at d:\Users\jinqing\.cargo\registry\src\github.com-1ecc6299db9ec823\lock_api-0.4.6\src\rwlock.rs:448
14: 0x7ff985aeadf3 - cgc::scene::SceneData::check_body_collide
at E:\gitlab\yserver\gc\src\scene.rs:99
...
81: 0x7ff9f29b7034 - BaseThreadInitThunk
82: 0x7ff9f2b02651 - RtlUserThreadStart
But the deadlock detection thread can not be changed to a tokio task,
because if deadlock happens, all tasks may be blocked, including the deadlock detection,
causing no deadlock error output.
In the following example, if the number of the deadlock tasks is larger than the thread number of tokio runtime,
all tasks will be blocked.
use std::{thread, time::Duration};
use parking_lot::RwLock;
use tokio::time;
#[tokio::main]
async fn main() {
tokio::spawn(async move {
for i in 0..999999 {
println!("{}", i);
time::sleep(Duration::from_secs(1)).await;
}
});
const MAX: i32 = 100;
for _ in 0..MAX {
tokio::spawn(async move {
{
// DEADLOCK!
let a = RwLock::new(());
let _g1 = a.write();
let _g2 = a.write();
}
time::sleep(Duration::from_secs(9999)).await;
});
}
println!("Hello, world!");
thread::sleep(Duration::from_secs(10));
}
The output is:
0
Hello, world!
_
If no deadlock, or the number of deadlock tasks is small, the output should be:
]]>TortoiseGit is OK but GitExtensions failshttp://www.shnenglu.com/jinq0123/archive/2022/04/24/229292.html閲戝簡閲戝簡Sun, 24 Apr 2022 06:01:00 GMThttp://www.shnenglu.com/jinq0123/archive/2022/04/24/229292.htmlhttp://www.shnenglu.com/jinq0123/comments/229292.htmlhttp://www.shnenglu.com/jinq0123/archive/2022/04/24/229292.html#Feedback0http://www.shnenglu.com/jinq0123/comments/commentRss/229292.htmlhttp://www.shnenglu.com/jinq0123/services/trackbacks/229292.htmlTortoiseGit is OK but GitExtensions fails
GitExtensions and Git fail:
"git" pull --progress "origin"
git@github.com: Permission denied (publickey).
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.
Done
Press Enter or Esc to exit...
But TortoiseGit is OK.
git.exe pull --progress -v --no-rebase "origin"
From github.com:jinq0123/recastnavigation
= [up to date] master -> origin/master
= [up to date] release-2.0 -> origin/release-2.0
Already up to date.
ssh -T shows that the reason is there is no id files:
PS d:\github> ssh -vT git@github.com
OpenSSH_for_Windows
debug1: Connecting to github.com [20.205.243.166] port 22.
debug1: Connection established.
debug1: identity file C:\\Users\\jinqing/.ssh/id_rsa type -1
debug1: identity file C:\\Users\\jinqing/.ssh/id_xmss-cert type -1
debug1: Authenticating to github.com:22 as 'git'
debug1: Host 'github.com' is known and matches the ED25519 host key.
debug1: Found key in C:\\Users\\jinqing/.ssh/known_hosts:2
debug1: Will attempt key: C:\\Users\\jinqing/.ssh/id_rsa
debug1: Will attempt key: C:\\Users\\jinqing/.ssh/id_xmss
debug1: SSH2_MSG_SERVICE_ACCEPT received
debug1: Authentications that can continue: publickey
debug1: Next authentication method: publickey
debug1: Trying private key: C:\\Users\\jinqing/.ssh/id_rsa
debug1: Trying private key: C:\\Users\\jinqing/.ssh/id_xmss
debug1: No more authentication methods to try.
git@github.com: Permission denied (publickey).
PS d:\github>
Copy my RSA key file rsa.ppk that TortoiseGit uses as ~/.ssh/id_rsa:
mod log;
use anyhow::Result;
use std::{thread, time::Duration};
use tracing::info;
fn main() -> Result<()> {
let _guard = log::init("./log", "example.log", "config/log_filter.txt")?;
for i in 0..999 {
info!(i, "Hello, world!");
thread::sleep(Duration::from_secs(1));
}
Ok(())
}
log.rs
//! Init log.
//!
use anyhow::{anyhow, Context as _, Result};
use hotwatch::{Event, Hotwatch};
use std::fs;
use std::path::Path;
use tracing::{debug, warn, Subscriber};
use tracing_appender::{non_blocking::WorkerGuard, rolling};
use tracing_subscriber::{fmt, layer::SubscriberExt, reload::Handle, EnvFilter};
/// Inits log.
/// Returns a WorkerGuard to ensure buffered logs are flushed,
/// and a Hotwatch to watch the log filter file.
pub fn init(
directory: impl AsRef<Path>,
file_name_prefix: impl AsRef<Path>,
log_filter_file: impl AsRef<Path>,
) -> Result<(WorkerGuard, Hotwatch)> {
let file_appender = rolling::daily(directory, file_name_prefix);
let (non_blocking, worker_guard) = tracing_appender::non_blocking(file_appender);
let file_layer = fmt::Layer::default()
.with_writer(non_blocking)
.json()
.flatten_event(true)
.with_ansi(false);
let builder = tracing_subscriber::fmt()
.pretty()
.with_env_filter(EnvFilter::from_default_env())
.with_filter_reloading();
let handle = builder.reload_handle();
let subscriber = builder.finish();
let subscriber = subscriber.with(file_layer);
tracing::subscriber::set_global_default(subscriber).context("set global default subscriber")?;
reload_filter(handle.clone(), log_filter_file.as_ref());
let log_filter_path_buf = log_filter_file.as_ref().to_path_buf();
let mut hotwatch = Hotwatch::new().context("hotwatch failed to initialize!")?;
hotwatch
.watch(log_filter_file.as_ref(), move |event: Event| {
debug!("log filter file event: {:?}", event);
if let Event::Write(_) = event {
reload_filter(handle.clone(), log_filter_path_buf.clone());
}
})
.with_context(|| format!("failed to watch file: {:?}", log_filter_file.as_ref()))?;
Ok((worker_guard, hotwatch))
}
fn reload_filter<S: Subscriber + 'static>(
handle: Handle<EnvFilter, S>,
log_filter_file: impl AsRef<Path>,
) {
let res = try_reload_filter(handle, log_filter_file);
match res {
Ok(_) => debug!("reload log filter OK"),
Err(e) => warn!("reload log filter error: {:?}", e),
}
}
fn try_reload_filter<S: Subscriber + 'static>(
handle: Handle<EnvFilter, S>,
log_filter_file: impl AsRef<Path>,
) -> Result<()> {
let contents = fs::read_to_string(log_filter_file.as_ref()).with_context(|| {
format!(
"something went wrong reading the file: {:?}",
log_filter_file.as_ref()
)
})?;
let contents = contents.trim();
debug!("reload log filter: {:?}", contents);
let new_filter = contents
.parse::<EnvFilter>()
.map_err(|e| anyhow!(e))
.context("failed to parse env filter")?;
handle.reload(new_filter).context("handle reload error")
}
log_filter.txt
log_filter.txt is the configure file for log.
The change of this file will trigger the reload.
The log filter file must exist, otherwise it will be error:
The content of log_filter.txt is a single line of filter string.
A filter string consists of one or more comma-separated directives.
The directive syntax is similar to RUST_LOG env val of env_logger’s.
Switch to tag tracing-0.1.29 first. The master's examples can not build.
Subscriber is renaming to collector on master.
tracingsubscriber in 0.1.29 has been renamed to tracingcollect on master.
In Applications
In Libraries
In async
The example is using tracing_subscriber::fmt, which can be configured by RUST_LOG env val.
By default, no log is output. Set RUST_LOG=info before running, and the log is:
Dec 25 10:23:11.200 INFO tracing_test: preparing to shave yaks number_of_yaks=3
Dec 25 10:23:11.203 INFO tracing_test: yak shaving completed. all_yaks_shaved=true
]]>Box<dyn Trait> doesn't implement the traithttp://www.shnenglu.com/jinq0123/archive/2021/12/21/217886.html閲戝簡閲戝簡Tue, 21 Dec 2021 07:09:00 GMThttp://www.shnenglu.com/jinq0123/archive/2021/12/21/217886.htmlhttp://www.shnenglu.com/jinq0123/comments/217886.htmlhttp://www.shnenglu.com/jinq0123/archive/2021/12/21/217886.html#Feedback0http://www.shnenglu.com/jinq0123/comments/commentRss/217886.htmlhttp://www.shnenglu.com/jinq0123/services/trackbacks/217886.html From:
By default a Box<dyn Trait> doesn't implement the trait of the object it contains. This means that trying to construct PeopleZoo<Box<dyn Person>> won't work out of the box and will give a type error.
Because of this, it's good practice to give a default implementation of your trait for it's boxed counterpart. This can be done by calling as_ref or as_mut on the Box and calling the references relevant method.
For just a small bit of effort you can help a bunch of people that may consume your struct.
]]>minikube DNS fails after SRV queryhttp://www.shnenglu.com/jinq0123/archive/2021/12/10/217878.html閲戝簡閲戝簡Fri, 10 Dec 2021 02:46:00 GMThttp://www.shnenglu.com/jinq0123/archive/2021/12/10/217878.htmlhttp://www.shnenglu.com/jinq0123/comments/217878.htmlhttp://www.shnenglu.com/jinq0123/archive/2021/12/10/217878.html#Feedback0http://www.shnenglu.com/jinq0123/comments/commentRss/217878.htmlhttp://www.shnenglu.com/jinq0123/services/trackbacks/217878.htmlminikube DNS fails after SRV query
(Jin Qing's Column, Dec., 2021)
My program is using K8s DNS SRV query to discovery service,
and when it's deployed on minikube, I find DNS failure.
I can use nslookup to reproduce the failure.
Querying a FQDN is OK. But after querying a non-existing SRV short name, the ping fails.
root@web-0:/# ping google.com
PING google.com (142.250.66.110) 56(84) bytes of data.
64 bytes from hkg12s28-in-f14.1e100.net (142.250.66.110): icmp_seq=1 ttl=108 time=33.7 ms
64 bytes from hkg12s28-in-f14.1e100.net (142.250.66.110): icmp_seq=2 ttl=108 time=33.8 ms
^C
--- google.com ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1000ms
rtt min/avg/max/mdev = 33.779/33.834/33.889/0.055 ms
root@web-0:/# nslookup
> set type=srv
> nosuch-nosuch-nosuch-1234567890abcdefg.cn
Server: 10.96.0.10
Address: 10.96.0.10#53
** server can't find nosuch-nosuch-nosuch-1234567890abcdefg.cn: NXDOMAIN
> exit
root@web-0:/# ping google.com
PING google.com (142.250.66.110) 56(84) bytes of data.
64 bytes from hkg12s28-in-f14.1e100.net (142.250.66.110): icmp_seq=1 ttl=108 time=33.7 ms
64 bytes from hkg12s28-in-f14.1e100.net (142.250.66.110): icmp_seq=2 ttl=108 time=33.7 ms
^C
--- google.com ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 33.730/33.735/33.741/0.183 ms
root@web-0:/# nslookup
> set type=srv
> nginx-wrong
Server: 10.96.0.10
Address: 10.96.0.10#53
** server can't find nginx-wrong: SERVFAIL
> exit
root@web-0:/# ping google.com
ping: unknown host google.com
root@web-0:/#
The ping will recover to normal after about 1 minute.
If I query a existing internal service name, and nslookup returns correctly, then DNS is OK after I quit nslookup.
root@web-0:/# ping google.com
PING google.com (142.250.66.110) 56(84) bytes of data.
64 bytes from hkg12s28-in-f14.1e100.net (142.250.66.110): icmp_seq=1 ttl=108 time=33.6 ms
64 bytes from hkg12s28-in-f14.1e100.net (142.250.66.110): icmp_seq=2 ttl=108 time=34.8 ms
^C
--- google.com ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 33.648/34.260/34.872/0.612 ms
root@web-0:/# nslookup
> set type=srv
> nginx
Server: 10.96.0.10
Address: 10.96.0.10#53
nginx.default.svc.cluster.local service = 0 25 80 web-1.nginx.default.svc.cluster.local.
nginx.default.svc.cluster.local service = 0 25 80 web-2.nginx.default.svc.cluster.local.
nginx.default.svc.cluster.local service = 0 25 80 web-0.nginx.default.svc.cluster.local.
nginx.default.svc.cluster.local service = 0 25 80 web-3.nginx.default.svc.cluster.local.
> exit
root@web-0:/# ping google.com
PING google.com (142.250.66.110) 56(84) bytes of data.
64 bytes from hkg12s28-in-f14.1e100.net (142.250.66.110): icmp_seq=1 ttl=108 time=33.5 ms
^C
--- google.com ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 33.529/33.529/33.529/0.000 ms
root@web-0:/#
When DNS fails, the whole cluster can not query any domain name outside,
but internal name is OK.
]]>DeathVoteExpirationTimeout in Orleanshttp://www.shnenglu.com/jinq0123/archive/2021/12/08/217872.html閲戝簡閲戝簡Wed, 08 Dec 2021 01:43:00 GMThttp://www.shnenglu.com/jinq0123/archive/2021/12/08/217872.htmlhttp://www.shnenglu.com/jinq0123/comments/217872.htmlhttp://www.shnenglu.com/jinq0123/archive/2021/12/08/217872.html#Feedback0http://www.shnenglu.com/jinq0123/comments/commentRss/217872.htmlhttp://www.shnenglu.com/jinq0123/services/trackbacks/217872.htmlDeathVoteExpirationTimeout in Orleans
(Jin Qing's Column, Dec., 2021)
Try to find out why Orleans need a DeathVoteExpirationTimeout config.
class LocalSiloHealthMonitor
{
...
private int CheckSuspectingNodes(DateTime now, List<string> complaints)
{
...
var freshVotes = membershipEntry.GetFreshVotes(now, DeathVoteExpirationTimeout);
...
}
...
}
class MembershipTableManager
{
...
public TryToSuspectOrKill(SiloAddress silo)
{
...
// get all valid (non-expired) votes
var freshVotes = entry.GetFreshVotes(DateTime.UtcNow, DeathVoteExpirationTimeout);
...
}
...
}
GetFreshVotes() uses this expiration time to ignore old voter:
internal GetFreshVotes(DateTime now, TimeSpan expiration)
{
...
foreach (var voter in this.SuspectTimes)
{
var otherVoterTime = voter.Item2;
if (now.Subtract(otherVoterTime) < expiration)
{
result.Add(voter);
}
}
return result.ToImmutable();
}
]]>How to delete local branches of GitExtensionhttp://www.shnenglu.com/jinq0123/archive/2021/12/07/217870.html閲戝簡閲戝簡Tue, 07 Dec 2021 09:04:00 GMThttp://www.shnenglu.com/jinq0123/archive/2021/12/07/217870.htmlhttp://www.shnenglu.com/jinq0123/comments/217870.htmlhttp://www.shnenglu.com/jinq0123/archive/2021/12/07/217870.html#Feedback0http://www.shnenglu.com/jinq0123/comments/commentRss/217870.htmlhttp://www.shnenglu.com/jinq0123/services/trackbacks/217870.htmlHow to delete local branches of GitExtension
(Jin Qing's Column, Dec., 2021)
GitExtension is a good tool. After a long time of usage, my branch list finally reaches over a full screen, and it is hard to select the branch I want.
GitExtension always remembers all the branches of remote and local, even they have been deleted.
I tried to find a way to delete these branches which are already merged, but resulted in futile.
It has a plugin names "Delete obsolete branches", but I don't know how to use it.
Finally I renamed the work directory, and cloned a new one, which clears all the branches.
It seems that these branches are stored in local .git directory.
If let GitExt open the renamed directory, these branches reappears.
]]>寤鴻鏃ュ織 slog 鏀規崲 tracinghttp://www.shnenglu.com/jinq0123/archive/2021/11/24/217865.html閲戝簡閲戝簡Wed, 24 Nov 2021 10:18:00 GMThttp://www.shnenglu.com/jinq0123/archive/2021/11/24/217865.htmlhttp://www.shnenglu.com/jinq0123/comments/217865.htmlhttp://www.shnenglu.com/jinq0123/archive/2021/11/24/217865.html#Feedback0http://www.shnenglu.com/jinq0123/comments/commentRss/217865.htmlhttp://www.shnenglu.com/jinq0123/services/trackbacks/217865.html寤鴻鏃ュ織 slog 鏀規崲 tracing
]]>Named parameters in Rusthttp://www.shnenglu.com/jinq0123/archive/2021/11/18/217862.html閲戝簡閲戝簡Thu, 18 Nov 2021 05:14:00 GMThttp://www.shnenglu.com/jinq0123/archive/2021/11/18/217862.htmlhttp://www.shnenglu.com/jinq0123/comments/217862.htmlhttp://www.shnenglu.com/jinq0123/archive/2021/11/18/217862.html#Feedback0http://www.shnenglu.com/jinq0123/comments/commentRss/217862.htmlhttp://www.shnenglu.com/jinq0123/services/trackbacks/217862.htmlNamed parameters in Rust
(Jin Qing's Column, Nov. 18, 2021)
There is no "named function parameters" in Rust,
but there is an idiom for this case.
If a function needs many parameters, we can define a type
with all these parameters as fields, and implement Default trait for this type.
Then we can input this type as the function parameter,
with some of the fields specified and others using default.
Rust traits are mechanism for adding behavior to types.
Traits have 2 modes. One is interface as Java.
Another is generic constraint. Generic functions are defined over types that implemented specific traits.
The "complie-time duck typing" in C++ templates is avoided in Rust.
Rust will reject a type with quack() method as a Duck type.
We must pass a type which implements Duck trait.
But in Go, a type with quack() method is sufficient to be used as a Duck interface.
]]>GLIBC_2.29 not foundhttp://www.shnenglu.com/jinq0123/archive/2021/11/05/217853.html閲戝簡閲戝簡Fri, 05 Nov 2021 01:42:00 GMThttp://www.shnenglu.com/jinq0123/archive/2021/11/05/217853.htmlhttp://www.shnenglu.com/jinq0123/comments/217853.htmlhttp://www.shnenglu.com/jinq0123/archive/2021/11/05/217853.html#Feedback0http://www.shnenglu.com/jinq0123/comments/commentRss/217853.htmlhttp://www.shnenglu.com/jinq0123/services/trackbacks/217853.htmlGLIBC_2.29 not found
(Jin Qing's Column, Nov. 4, 2021)
My Rust program failed when running in docker:
root@a26b49c91efb:/myapp# ldd libmyapp_py.so
./libmyapp_py.so: /lib/x86_64-linux-gnu/libm.so.6: version `GLIBC_2.29' not found (required by ./libmyapp_py.so)
The problem is because I build in "rust:1.56", and run in "debian:buster-slim" which is quite old. Run dpkg -l libc-bin shows the libc version is 2.28:
C:\Users\jinqing01>docker run debian:buster-slim dpkg -l libc-bin
Unable to find image 'debian:buster-slim' locally
buster-slim: Pulling from library/debian
b380bbd43752: Already exists
Digest: sha256:544c93597c784cf68dbe492ef35c00de7f4f6a990955c7144a40b20d86a3475f
Status: Downloaded newer image for debian:buster-slim
Desired=Unknown/Install/Remove/Purge/Hold
| Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend
|/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad)
||/ Name Version Architecture Description
+++-==============-============-============-=================================
ii libc-bin 2.28-10 amd64 GNU C Library: Binaries
Using the latest "debian:bookworm-slim" solved the problem.
Dockerfile:
FROM rust:1.56 as chef
RUN cargo install cargo-chef
WORKDIR /myapp
FROM chef AS planner
COPY . .
RUN cargo chef prepare --recipe-path recipe.json
FROM chef AS builder
COPY --from=planner /myapp/recipe.json recipe.json
# Build dependencies
RUN cargo chef cook --release --recipe-path recipe.json
# Build application
COPY . .
RUN cargo build --release
FROM debian:bookworm-slim AS runtime
WORKDIR /myapp
RUN apt-get update && apt-get install -y python3 python3-pip && rm -rf /var/lib/apt/lists/*
RUN pip3 install protobuf
COPY --from=builder /myapp/target/release/*.so /myapp/
COPY --from=builder /myapp/target/release/myapp /myapp/
COPY --from=builder /myapp/tests /myapp/tests
CMD ["myapp"]
]]>Clustering provider in Orleanshttp://www.shnenglu.com/jinq0123/archive/2021/11/03/217852.html閲戝簡閲戝簡Wed, 03 Nov 2021 05:36:00 GMThttp://www.shnenglu.com/jinq0123/archive/2021/11/03/217852.htmlhttp://www.shnenglu.com/jinq0123/comments/217852.htmlhttp://www.shnenglu.com/jinq0123/archive/2021/11/03/217852.html#Feedback0http://www.shnenglu.com/jinq0123/comments/commentRss/217852.htmlhttp://www.shnenglu.com/jinq0123/services/trackbacks/217852.htmlClustering provider in Orleans
(Jin Qing's Column, Nov. 3, 2021)
When deployed to a cluster of nodes,
Orleans internally implements a protocol to manage it's silos,
including discovery, failure and reconfigure,
which is called cluster membership management.
Orleans has clustering membership providers for: Azure, SQL server, Zookeeper.
Clustering provider is one of key aspects of silo configuration.
]]>Why Orleans' actor is virutalhttp://www.shnenglu.com/jinq0123/archive/2021/11/02/217850.html閲戝簡閲戝簡Tue, 02 Nov 2021 07:27:00 GMThttp://www.shnenglu.com/jinq0123/archive/2021/11/02/217850.htmlhttp://www.shnenglu.com/jinq0123/comments/217850.htmlhttp://www.shnenglu.com/jinq0123/archive/2021/11/02/217850.html#Feedback0http://www.shnenglu.com/jinq0123/comments/commentRss/217850.htmlhttp://www.shnenglu.com/jinq0123/services/trackbacks/217850.htmlWhy Orleans' actor is virutal
(Jin Qing's Column, Nov. 2, 2021)
Virtual Actor is a concept invented by Microsoft Orleans, which is a framework of distributed actor.
The virtual actor is analogous to virtual memory. Virtual actors are mapped to physical arctors instances in the running servers. Virtualization of actors in Orleans has 4 facets:
Perpetual existence
Actors always exist, virtually
Actors can not be created or destroied explicitly
Server failure does not affect the actors' existence
Automatic instantiation
Activation: Orleans automatically create an actor
A request triggers an activation if the actor doesn't exist
Unused actors are automatically reclaimed
Location transparency
Applications don't know where the physical actor is
Similar to virtual memory's "paged out" and mapping
Automatic scale out
2 activation modes:
Single activation (default): Only one simultaneous actor is allowed
Stateless worker: Many activations of an actor are created
to increase throughput
Actor viruliaztion greatly simplifes the programming, since it gets rid of the burden of actor lifecycle control.
]]>What comes after microservice?http://www.shnenglu.com/jinq0123/archive/2021/10/29/217843.html閲戝簡閲戝簡Fri, 29 Oct 2021 03:16:00 GMThttp://www.shnenglu.com/jinq0123/archive/2021/10/29/217843.htmlhttp://www.shnenglu.com/jinq0123/comments/217843.htmlhttp://www.shnenglu.com/jinq0123/archive/2021/10/29/217843.html#Feedback0http://www.shnenglu.com/jinq0123/comments/commentRss/217843.htmlhttp://www.shnenglu.com/jinq0123/services/trackbacks/217843.htmlWhat comes after microservice?
(Jin Qing's Column, Oct. 25, 2021)
Reading "The Evolution of Distributed Systems on Kubernetes" from Bilgin Ibryam.
]]>Advice for Rust library writers about Errorhttp://www.shnenglu.com/jinq0123/archive/2021/09/02/217805.html閲戝簡閲戝簡Thu, 02 Sep 2021 03:24:00 GMThttp://www.shnenglu.com/jinq0123/archive/2021/09/02/217805.htmlhttp://www.shnenglu.com/jinq0123/comments/217805.htmlhttp://www.shnenglu.com/jinq0123/archive/2021/09/02/217805.html#Feedback0http://www.shnenglu.com/jinq0123/comments/commentRss/217805.htmlhttp://www.shnenglu.com/jinq0123/services/trackbacks/217805.htmlAdvice for library writers
Idioms for Rust libraries are still forming, but if your library needs to report custom errors, then you should probably define your own error type. It’s up to you whether or not to expose its representation (like ErrorKind) or keep it hidden (like ParseIntError). Regardless of how you do it, it’s usually good practice to at least provide some information about the error beyond just its String representation. But certainly, this will vary depending on use cases.
At a minimum, you should probably implement the Error trait. This will give users of your library some minimum flexibility for composing errors. Implementing the Error trait also means that users are guaranteed the ability to obtain a string representation of an error (because it requires impls for both fmt::Debug and fmt::Display).
Beyond that, it can also be useful to provide implementations of From on your error types. This allows you (the library author) and your users to compose more detailed errors. For example, csv::Error provides From impls for both io::Error and byteorder::Error.
Finally, depending on your tastes, you may also want to define a Result type alias, particularly if your library defines a single error type. This is used in the standard library for io::Result and fmt::Result.
]]>How to show the first few errors of rustchttp://www.shnenglu.com/jinq0123/archive/2021/09/01/217804.html閲戝簡閲戝簡Wed, 01 Sep 2021 02:55:00 GMThttp://www.shnenglu.com/jinq0123/archive/2021/09/01/217804.htmlhttp://www.shnenglu.com/jinq0123/comments/217804.htmlhttp://www.shnenglu.com/jinq0123/archive/2021/09/01/217804.html#Feedback0http://www.shnenglu.com/jinq0123/comments/commentRss/217804.htmlhttp://www.shnenglu.com/jinq0123/services/trackbacks/217804.html
# How to show the first few errors of rustc
(Jin Qing's Column, Sep. 1, 2021)
When using VS Code to develop Rust, "cargo check" output maybe thousands of lines.
Use "cargo check --color=always 2>&1 | head -n100" to limit the output.
1. Change powershell terminal to cmd, because powershell fails to use pipe
* Terminal -> New Terminal to open a terminal
* Open the dropdown list by click the down arrow of "+" on the top-left of terminal
* Select "Command Prompt"
2. Type and run command: `cargo test 2>&1 --color always | "d:\Program Files\Git\usr\bin\head.exe" -n100`
]]>&'a mut self is restrictivehttp://www.shnenglu.com/jinq0123/archive/2021/08/25/217794.html閲戝簡閲戝簡Wed, 25 Aug 2021 05:32:00 GMThttp://www.shnenglu.com/jinq0123/archive/2021/08/25/217794.htmlhttp://www.shnenglu.com/jinq0123/comments/217794.htmlhttp://www.shnenglu.com/jinq0123/archive/2021/08/25/217794.html#Feedback0http://www.shnenglu.com/jinq0123/comments/commentRss/217794.htmlhttp://www.shnenglu.com/jinq0123/services/trackbacks/217794.html
# &'a mut self is restrictive
From https://github.com/pretzelhammer/rust-blog/blob/master/posts/common-rust-lifetime-misconceptions.md#5-if-it-compiles-then-my-lifetime-annotations-are-correct
```
#[derive(Debug)]
struct NumRef<'a>(&'a i32);
impl<'a> NumRef<'a> {
// my struct is generic over 'a so that means I need to annotate
// my self parameters with 'a too, right? (answer: no, not right)
fn some_method(&'a mut self) {}
}
fn main() {
let mut num_ref = NumRef(&5);
num_ref.some_method(); // mutably borrows num_ref for the rest of its lifetime
num_ref.some_method(); // 鉂?/div>
println!("{:?}", num_ref); // 鉂?/div>
}
```
If we have some struct generic over 'a we almost never want to write a method with a &'a mut self receiver. What we're communicating to Rust is "this method will mutably borrow the struct for the entirety of the struct's lifetime". In practice this means Rust's borrow checker will only allow at most one call to some_method before the struct becomes permanently mutably borrowed and thus unusable. The use-cases for this are extremely rare but the code above is very easy for confused beginners to write and it compiles. The fix is to not add unnecessary explicit lifetime annotations and let Rust's lifetime elision rules handle it:
]]>Rust variable rebinding for closurehttp://www.shnenglu.com/jinq0123/archive/2021/08/22/217787.html閲戝簡閲戝簡Sun, 22 Aug 2021 09:25:00 GMThttp://www.shnenglu.com/jinq0123/archive/2021/08/22/217787.htmlhttp://www.shnenglu.com/jinq0123/comments/217787.htmlhttp://www.shnenglu.com/jinq0123/archive/2021/08/22/217787.html#Feedback0http://www.shnenglu.com/jinq0123/comments/commentRss/217787.htmlhttp://www.shnenglu.com/jinq0123/services/trackbacks/217787.html From: https://rust-unofficial.github.io/patterns/idioms/pass-var-to-closure.html
Rust closure captures variables by reference by default. You can use "move" keyword to change to move variables. In most cases, some variables are moved, and some are referenced. We can rebind variables in a separate scope before move to the closure.
``` use std::rc::Rc;
let num1 = Rc::new(1); let num2 = Rc::new(2); let num3 = Rc::new(3); let closure = { // `num1` is moved let num2 = num2.clone(); // `num2` is cloned let num3 = num3.as_ref(); // `num3` is borrowed move || { *num1 + *num2 + *num3; } }; ```
Or we can use additional variables to move in the same scope: ``` let num2_clone = num2.clone(); let num3_ref = num3.as_ref(); let closure = move || { *num1 + *num2_clone + *num3_ref } ```
Using separate scope can reuse the variable name, which make the closure body clearer, but need additional indent.
Never use unwrap on Result. If the type is Err, it will panic and crash the program. The only exception is if it has already been checked for error previously or in test code.
Never use unwrap on Option for the same reason if the type is None as Result is Err.
]]>Rust visibilityhttp://www.shnenglu.com/jinq0123/archive/2021/08/09/217774.html閲戝簡閲戝簡Mon, 09 Aug 2021 05:45:00 GMThttp://www.shnenglu.com/jinq0123/archive/2021/08/09/217774.htmlhttp://www.shnenglu.com/jinq0123/comments/217774.htmlhttp://www.shnenglu.com/jinq0123/archive/2021/08/09/217774.html#Feedback0http://www.shnenglu.com/jinq0123/comments/commentRss/217774.htmlhttp://www.shnenglu.com/jinq0123/services/trackbacks/217774.htmlVisibility and privacy - The Rust Reference (rust-lang.org)
In addition to public and private, Rust allows users to declare an item as visible only within a given scope. The rules for pub restrictions are as follows:
pub(in path) makes an item visible within the provided path. path must be an ancestor module of the item whose visibility is being declared.
pub(crate) makes an item visible within the current crate.
pub(super) makes an item visible to the parent module. This is equivalent to pub(in super).
pub(self) makes an item visible to the current module. This is equivalent to pub(in self) or not using pub at all.
]]>Why does Rust check borrow even in single threadhttp://www.shnenglu.com/jinq0123/archive/2021/08/07/217773.html閲戝簡閲戝簡Sat, 07 Aug 2021 08:05:00 GMThttp://www.shnenglu.com/jinq0123/archive/2021/08/07/217773.htmlhttp://www.shnenglu.com/jinq0123/comments/217773.htmlhttp://www.shnenglu.com/jinq0123/archive/2021/08/07/217773.html#Feedback0http://www.shnenglu.com/jinq0123/comments/commentRss/217773.htmlhttp://www.shnenglu.com/jinq0123/services/trackbacks/217773.html
# Why does Rust check borrow even in single thread
(Jin Qing's Column, Aug. 7, 2021)
The Rust book says borrow checking is to prevent data race.
But the borrow checker forbids multiply mutable borrows even in the same thread.
Is there data race in single thread?
Why does the borrow checker forbid it in the same thread?
[The Problem With Single-threaded Shared Mutability](https://manishearth.github.io/blog/2015/05/17/the-problem-with-shared-mutability/)
answers this question.
It gaves 2 cases that shared mutability causes prolem.
One is Rust enum variable, which can has different inner type.
If the inner type changed, the references to the old data would be invalidated.
Another case is Iterator invalidation that the container's change can invalidate the Iterator.
How to do multiple actions on Event BeginPlay in UE4 Blueprints?
Sams-Teach-Yourself-Unreal-Engine-4-Game-Development-in-24-Hours says: ``` Q. When I try to add a second event node, BeginPlay, the Editor shows me the first one already placed in the Event Graph. Why does this happen?
A. Some events, such as Event Tick and the BeginPlay event, can have only one instance per Blueprint. ```
https://forums.unrealengine.com/t/do-multiple-things-on-event-begin-play/411/10 ``` The Sequence node allows for a single execution pulse to trigger a series of events in order. The node may have any number of outputs, all of which get called as soon as the Sequence node receives an input. They will always get called in order, but without any delay. To a typical user, the outputs will likely appear to have been triggered simultaneously. ```
Youtube video: [How to do Multiple Actions on Event Begin Play Unreal Engine 4 Blueprints](https://www.youtube.com/watch?v=nqG-ztbs230)
Discovered by my colleague Shen Yichai: ``` Share a interesting MS Build bug: For file a.cpp, enable optimization for Win64 or XSX. The MSBuild always compiling without error and ending (infinite compile). The PS5 (clang) does not have this issue. ```
The simplified code: ``` struct Tag { int v; };
void Goo(Tag* r, Tag* a) { r->v = a->v; }
void Foo(Tag* pos0, Tag* pos) { for (int j = 0; j < 4; j++) { if (j == 0) { for (int i = 0; i < 8; i++) { Goo(pos0++, pos); Goo(pos0++, pos); } } else { for (int i = 0; i < 8; i++) { Goo(pos0++, pos); Goo(pos0++, pos); } } } }
int main() { Foo(nullptr, nullptr); } ```
The default release configuration can build correctly. But if "Properties -> C/C++ -> Optimization -> Whole Program optimization" (/GL) of file or project is changed to "No", the compilation will take a long time as 5 minutes.
``` Microsoft Visual Studio Community 2019 Version 16.9.5 VisualStudio.16.Release/16.9.5+31229.75 Microsoft .NET Framework Version 4.8.04084 ```
]]>Fbx File Format Identifierhttp://www.shnenglu.com/jinq0123/archive/2021/05/30/217692.html閲戝簡閲戝簡Sun, 30 May 2021 01:55:00 GMThttp://www.shnenglu.com/jinq0123/archive/2021/05/30/217692.htmlhttp://www.shnenglu.com/jinq0123/comments/217692.htmlhttp://www.shnenglu.com/jinq0123/archive/2021/05/30/217692.html#Feedback0http://www.shnenglu.com/jinq0123/comments/commentRss/217692.htmlhttp://www.shnenglu.com/jinq0123/services/trackbacks/217692.html (閲戝簡鐨勪笓鏍?2021.5)
Print the list of FBX file format identifiers.
FBX SDK FbxExporter can set the file format identifier which is an int parameter. The commnet says: User does not need to specify it by default. If not specified, plugin will detect the file format according to file suffix automatically.
FBX SDK 2020 automatically set it to the latest binary format, which can not be parsed by "Autodesk FBX Converter 2013". We need to specify the format identifier for FBX Converter 2013. But there is no specification of this parameter. We use the following code to display all the valid format identifiers and their descriptions.
``` #include <fbxsdk.h> #include <cassert>
int main() { FbxManager* myManager = FbxManager::Create(); assert(myManager);
int lFormatCount = myManager->GetIOPluginRegistry()->GetWriterFormatCount(); for (int lFormatIndex = 0; lFormatIndex < lFormatCount; lFormatIndex++) { FbxString lDesc = myManager->GetIOPluginRegistry()->GetWriterFormatDescription(lFormatIndex); printf("Format index %d: %s\n", lFormatIndex, (const char*)lDesc); } return 0; } ```
Output: ``` Format index 0: FBX binary (*.fbx) Format index 1: FBX ascii (*.fbx) Format index 2: FBX encrypted (*.fbx) Format index 3: FBX 6.0 binary (*.fbx) Format index 4: FBX 6.0 ascii (*.fbx) Format index 5: FBX 6.0 encrypted (*.fbx) Format index 6: AutoCAD DXF (*.dxf) Format index 7: Alias OBJ (*.obj) Format index 8: Collada DAE (*.dae) Format index 9: Biovision BVH (*.bvh) Format index 10: Motion Analysis HTR (*.htr) Format index 11: Motion Analysis TRC (*.trc) Format index 12: Acclaim ASF (*.asf) Format index 13: Acclaim AMC (*.amc) Format index 14: Vicon C3D (*.c3d) Format index 15: Adaptive Optics AOA (*.aoa) Format index 16: Superfluo MCD (*.mcd) ```
FBX Converter 2013 will error "File is corrupted" for files exported as *.fbx file format: -1, 0, 2, 5. Ascii format (1, 4) and FBX 6.0 binary (3) are OK.
]]>絎?浠f父鎴忎富鏈?/title>http://www.shnenglu.com/jinq0123/archive/2021/05/09/217680.html閲戝簡閲戝簡Sun, 09 May 2021 12:44:00 GMThttp://www.shnenglu.com/jinq0123/archive/2021/05/09/217680.htmlhttp://www.shnenglu.com/jinq0123/comments/217680.htmlhttp://www.shnenglu.com/jinq0123/archive/2021/05/09/217680.html#Feedback0http://www.shnenglu.com/jinq0123/comments/commentRss/217680.htmlhttp://www.shnenglu.com/jinq0123/services/trackbacks/217680.html 鍘熸枃錛歔Ninth generation of video game consoles](https://en.wikipedia.org/wiki/Ninth_generation_of_video_game_consoles)
2020.11錛屽井杞?MS) Xbox Series X/S 鍜?Sony PlayStation 5 (PS5) 鍙戝竷錛屾爣蹇楃潃娓告垙涓繪満榪涘叆絎?浠c?br /> 鍜屽墠浠g殑 Xbox One 鍜?PS4 鐩告瘮錛屾柊涓浠d富鏈烘湁鍙鐨勬ц兘鎻愬崌錛屾敮鎸佸疄鏃跺厜綰胯窡韙紝4K鍒嗚鯨鐜囷紝鐩爣甯х巼涓?0銆?br />鍐呴儴閮戒嬌鐢ㄤ簡鍥烘佺‖鐩?SSD)銆備綆閰嶇増娌℃湁鍏夐┍錛屼粎鏀寔緗戠粶鍜孶SB銆?br /> 瀹氫綅涓婅鑳滆繃浠誨ぉ鍫係witch鍜屼簯娓告垙鏈嶅姟濡?Stadia 鍜?Amazon Luna.
## 鑳屾櫙
絎?浠f椂闂磋緝闀褲傚洜涓烘懇灝斿畾寰嬶紝榪囧幓鍑犱唬涓鑸瘡浠d負5騫存椂闂達紝浣嗘槸 MS 鍜?Sony 鍑轟簡涓棿浠d駭鍝?Xbox One X 鍜?PS4 Pro.
Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call. ```
async void CountDown(int count) { for (int i = count; i >= 0; i--) { await Task.Delay(1000); } } ```
> Never call `async Task` methods without also awaiting on the returned Task. If you don’t want to wait for the async behaviour to complete, you should call an `async void` method instead.
async Task CountDownAsync(int count) { for (int i = count; i >= 0; i--) { await Task.Delay(1000); } throw new Exception(); } ```
濡傛灉鏄皟鐢ㄨ繑鍥?void 鐨勫紓姝ユ柟娉曪紝Unity 浼氭姤閿欙細 ``` Exception: Exception of type 'System.Exception' was thrown. ```
## 瀵?Async 鍚庣紑鐨勮鏄?br /> ``` You could say that the Async suffix convention is to communicate to the API user that the method is awaitable. For a method to be awaitable, it must return Task for a void, or Task<T> for a value-returning method, which means only the latter can be suffixed with Async. ```