Skip to content

Commit

Permalink
Merge pull request #82 from niklasf/append-ascii
Browse files Browse the repository at this point in the history
Optimize string conversions
  • Loading branch information
niklasf authored Aug 31, 2024
2 parents f95e03e + 18cc653 commit 4fd70a3
Show file tree
Hide file tree
Showing 9 changed files with 394 additions and 109 deletions.
14 changes: 12 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,24 @@ jobs:
flags: "-Z minimal-versions --all-features"
runs-on: ubuntu-latest
steps:
- run: sudo apt-get update && sudo apt-get install -y valgrind
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.toolchain }}
- run: cargo test --no-default-features ${{ matrix.flags }}
- run: cargo doc --no-default-features ${{ matrix.flags }}
- run: cargo bench --no-default-features ${{ matrix.flags }}
bench:
runs-on: ubuntu-latest
steps:
- run: sudo apt-get update && sudo apt-get install -y valgrind
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- run: cargo bench
check_fuzz:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
- run: cargo check --manifest-path fuzz/Cargo.toml
check_no_std:
runs-on: ubuntu-latest
Expand Down
4 changes: 3 additions & 1 deletion benches/benches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,12 @@ fn bench_zobrist_hash() -> Zobrist64 {
}

fn bench_fen_roundtrip() -> String {
let mut buffer = String::new();
black_box("rnbqkb1r/1p3ppp/p2p1n2/4p3/3NP3/2N1B3/PPP2PPP/R2QKB1R w KQkq - 0 7")
.parse::<Fen>()
.expect("valid fen")
.to_string()
.append_to_string(&mut buffer);
buffer
}

iai::main!(
Expand Down
161 changes: 113 additions & 48 deletions src/fen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,22 +50,22 @@
//! ```
use core::{
char, fmt,
fmt::{Display, Write as _},
char,
fmt::{self, Display},
num::NonZeroU32,
str::FromStr,
};

use crate::{
Bitboard, Board, ByColor, ByRole, CastlingMode, Color, EnPassantMode, File, FromSetup, Piece,
Position, PositionError, Rank, RemainingChecks, Role, Setup, Square,
util::AppendAscii, Bitboard, Board, ByColor, ByRole, CastlingMode, Color, EnPassantMode, File,
FromSetup, Piece, Position, PositionError, Rank, RemainingChecks, Role, Setup, Square,
};

fn fmt_castling(
f: &mut fmt::Formatter<'_>,
fn append_castling<W: AppendAscii>(
f: &mut W,
board: &Board,
castling_rights: Bitboard,
) -> fmt::Result {
) -> Result<(), W::Error> {
let mut empty = true;

for color in Color::ALL {
Expand All @@ -76,7 +76,7 @@ fn fmt_castling(
let candidates = board.by_piece(color.rook()) & color.backrank();

for rook in (castling_rights & color.backrank()).into_iter().rev() {
f.write_char(
f.append_ascii(
if Some(rook) == candidates.first() && king.map_or(false, |k| rook < k) {
color.fold_wb('Q', 'q')
} else if Some(rook) == candidates.last() && king.map_or(false, |k| k < rook) {
Expand All @@ -91,42 +91,46 @@ fn fmt_castling(
}

if empty {
f.write_char('-')?;
f.append_ascii('-')?;
}

Ok(())
}

fn fmt_pockets(f: &mut fmt::Formatter<'_>, pockets: &ByColor<ByRole<u8>>) -> fmt::Result {
f.write_char('[')?;
fn append_pockets<W: AppendAscii>(
f: &mut W,
pockets: &ByColor<ByRole<u8>>,
) -> Result<(), W::Error> {
f.append_ascii('[')?;
for color in Color::ALL {
for role in Role::ALL {
let piece = Piece { color, role };
for _ in 0..*pockets.piece(piece) {
f.write_char(piece.char())?;
f.append_ascii(piece.char())?;
}
}
}
f.write_char(']')
f.append_ascii(']')
}

fn fmt_epd(f: &mut fmt::Formatter<'_>, setup: &Setup) -> fmt::Result {
setup.board.board_fen(setup.promoted).fmt(f)?;
fn append_epd<W: AppendAscii>(f: &mut W, setup: &Setup) -> Result<(), W::Error> {
f.reserve(21);
setup.board.board_fen(setup.promoted).append_to(f)?;
if let Some(ref pockets) = setup.pockets {
fmt_pockets(f, pockets)?;
append_pockets(f, pockets)?;
}
f.write_char(' ')?;
f.write_char(setup.turn.char())?;
f.write_char(' ')?;
fmt_castling(f, &setup.board, setup.castling_rights)?;
f.write_char(' ')?;
f.append_ascii(' ')?;
f.append_ascii(setup.turn.char())?;
f.append_ascii(' ')?;
append_castling(f, &setup.board, setup.castling_rights)?;
f.append_ascii(' ')?;
match setup.ep_square {
Some(ref ep_square) => Display::fmt(ep_square, f)?,
None => f.write_char('-')?,
Some(ref ep_square) => ep_square.append_to(f)?,
None => f.append_ascii('-')?,
}
if let Some(ref remaining_checks) = setup.remaining_checks {
f.write_char(' ')?;
Display::fmt(remaining_checks, f)?;
f.append_ascii(' ')?;
remaining_checks.append_to(f)?;
}
Ok(())
}
Expand Down Expand Up @@ -272,7 +276,7 @@ impl FromStr for Board {

impl Display for Board {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.board_fen(Bitboard(0)).fmt(f)
self.board_fen(Bitboard(0)).append_to(f)
}
}

Expand All @@ -286,39 +290,59 @@ pub struct BoardFen<'b> {
promoted: Bitboard,
}

impl<'b> Display for BoardFen<'b> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
impl BoardFen<'_> {
fn append_to<W: AppendAscii>(&self, f: &mut W) -> Result<(), W::Error> {
f.reserve(15);

for rank in Rank::ALL.into_iter().rev() {
let mut empty = 0;
let mut prev_file = -1;

for file in File::ALL {
let square = Square::from_coords(file, rank);
for square in self.board.occupied() & rank {
let empty = i32::from(square.file()) - prev_file - 1;
if empty > 0 {
f.append_ascii(char::from(b'0' + empty as u8))?;
}
prev_file = i32::from(square.file());

empty = if let Some(piece) = self.board.piece_at(square) {
if empty > 0 {
f.write_char(char::from_digit(empty, 10).expect("8 files only"))?;
}
f.write_char(piece.char())?;
if self.promoted.contains(square) {
f.write_char('~')?;
}
0
} else {
empty + 1
};
f.append_ascii(self.board.piece_at(square).expect("piece").char())?;
if self.promoted.contains(square) {
f.append_ascii('~')?;
}
}

let empty = i32::from(File::H) - prev_file;
if empty > 0 {
f.write_char(char::from_digit(empty, 10).expect("8 files only"))?;
f.append_ascii(char::from(b'0' + empty as u8))?;
}

if rank > Rank::First {
f.write_char('/')?;
f.append_ascii('/')?;
}
}

Ok(())
}

#[cfg(feature = "alloc")]
pub fn append_to_string(&self, s: &mut alloc::string::String) {
let _ = self.append_to(s);
}

#[cfg(feature = "alloc")]
pub fn append_ascii_to(&self, buf: &mut alloc::vec::Vec<u8>) {
let _ = self.append_to(buf);
}

#[cfg(feature = "std")]
pub fn write_ascii_to<W: std::io::Write>(&self, w: W) -> std::io::Result<()> {
self.append_to(&mut crate::util::WriteAscii(w))
}
}

impl Display for BoardFen<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.append_to(f)
}
}

/// A FEN like `rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1`.
Expand Down Expand Up @@ -505,6 +529,29 @@ impl Fen {
pub fn into_position<P: FromSetup>(self, mode: CastlingMode) -> Result<P, PositionError<P>> {
P::from_setup(self.0, mode)
}

fn append_to<W: AppendAscii>(&self, f: &mut W) -> Result<(), W::Error> {
append_epd(f, &self.0)?;
f.append_ascii(' ')?;
f.append_u32(self.0.halfmoves)?;
f.append_ascii(' ')?;
f.append_u32(u32::from(self.0.fullmoves))
}

#[cfg(feature = "alloc")]
pub fn append_to_string(&self, s: &mut alloc::string::String) {
let _ = self.append_to(s);
}

#[cfg(feature = "alloc")]
pub fn append_ascii_to(&self, buf: &mut alloc::vec::Vec<u8>) {
let _ = self.append_to(buf);
}

#[cfg(feature = "std")]
pub fn write_ascii_to<W: std::io::Write>(&self, w: W) -> std::io::Result<()> {
self.append_to(&mut crate::util::WriteAscii(w))
}
}

impl From<Setup> for Fen {
Expand All @@ -529,8 +576,7 @@ impl FromStr for Fen {

impl Display for Fen {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt_epd(f, &self.0)?;
write!(f, " {} {}", self.0.halfmoves, self.0.fullmoves)
self.append_to(f)
}
}

Expand Down Expand Up @@ -571,6 +617,25 @@ impl Epd {
pub fn into_position<P: FromSetup>(self, mode: CastlingMode) -> Result<P, PositionError<P>> {
P::from_setup(self.into_setup(), mode)
}

fn append_to<W: AppendAscii>(&self, f: &mut W) -> Result<(), W::Error> {
append_epd(f, &self.0)
}

#[cfg(feature = "alloc")]
pub fn append_to_string(&self, s: &mut alloc::string::String) {
let _ = self.append_to(s);
}

#[cfg(feature = "alloc")]
pub fn append_ascii_to(&self, buf: &mut alloc::vec::Vec<u8>) {
let _ = self.append_to(buf);
}

#[cfg(feature = "std")]
pub fn write_ascii_to<W: std::io::Write>(&self, w: W) -> std::io::Result<()> {
self.append_to(&mut crate::util::WriteAscii(w))
}
}

impl From<Setup> for Epd {
Expand All @@ -595,7 +660,7 @@ impl FromStr for Epd {

impl Display for Epd {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt_epd(f, &self.0)
self.append_to(f)
}
}

Expand Down
14 changes: 9 additions & 5 deletions src/position.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,19 @@ impl Outcome {
_ => return Err(ParseOutcomeError::Invalid),
})
}
}

impl fmt::Display for Outcome {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match *self {
pub const fn as_str(self) -> &'static str {
match self {
Outcome::Decisive { winner: White } => "1-0",
Outcome::Decisive { winner: Black } => "0-1",
Outcome::Draw => "1/2-1/2",
})
}
}
}

impl fmt::Display for Outcome {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}

Expand Down
Loading

0 comments on commit 4fd70a3

Please sign in to comment.