mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Attempted generator-driven AI
This commit is contained in:
parent
0b4d3c9e20
commit
558b5f7c3a
@ -10,12 +10,16 @@ use rand::prelude::*;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use slotmap::HopSlotMap;
|
use slotmap::HopSlotMap;
|
||||||
use std::{
|
use std::{
|
||||||
any::Any,
|
any::{Any, TypeId},
|
||||||
collections::VecDeque,
|
collections::VecDeque,
|
||||||
ops::{ControlFlow, Deref, DerefMut},
|
ops::{ControlFlow, Deref, DerefMut, Generator, GeneratorState},
|
||||||
|
sync::{Arc, atomic::{AtomicPtr, Ordering}},
|
||||||
|
pin::Pin,
|
||||||
|
marker::PhantomData,
|
||||||
};
|
};
|
||||||
use vek::*;
|
use vek::*;
|
||||||
use world::{civ::Track, site::Site as WorldSite, util::RandomPerm};
|
use world::{civ::Track, site::Site as WorldSite, util::RandomPerm};
|
||||||
|
use crate::rule::npc_ai;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Default)]
|
#[derive(Copy, Clone, Default)]
|
||||||
pub enum NpcMode {
|
pub enum NpcMode {
|
||||||
@ -153,6 +157,110 @@ impl TaskState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub unsafe trait Context {
|
||||||
|
// TODO: Somehow we need to enforce this bound, I think?
|
||||||
|
// Hence, this trait is unsafe for now.
|
||||||
|
type Ty<'a>;// where for<'a> Self::Ty<'a>: 'a;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Data<C: Context>(Arc<AtomicPtr<()>>, PhantomData<C>);
|
||||||
|
|
||||||
|
impl<C: Context> Clone for Data<C> {
|
||||||
|
fn clone(&self) -> Self { Self(self.0.clone(), PhantomData) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: Context> Data<C> {
|
||||||
|
pub fn with<R>(&mut self, f: impl FnOnce(&mut C::Ty<'_>) -> R) -> R {
|
||||||
|
let ptr = self.0.swap(std::ptr::null_mut(), Ordering::Acquire);
|
||||||
|
if ptr.is_null() {
|
||||||
|
panic!("Data pointer was null, you probably tried to access data recursively")
|
||||||
|
} else {
|
||||||
|
// Safety: We have exclusive access to the pointer within this scope.
|
||||||
|
// TODO: Do we need a panic guard here?
|
||||||
|
let r = f(unsafe { &mut *(ptr as *mut C::Ty<'_>) });
|
||||||
|
self.0.store(ptr, Ordering::Release);
|
||||||
|
r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Priority = usize;
|
||||||
|
|
||||||
|
pub struct TaskBox<C: Context, A = ()> {
|
||||||
|
task: Option<(
|
||||||
|
TypeId,
|
||||||
|
Box<dyn Generator<Data<C>, Yield = A, Return = ()> + Unpin + Send + Sync>,
|
||||||
|
Priority,
|
||||||
|
)>,
|
||||||
|
data: Data<C>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: Context, A> TaskBox<C, A> {
|
||||||
|
pub fn new(data: Data<C>) -> Self {
|
||||||
|
Self {
|
||||||
|
task: None,
|
||||||
|
data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn finish(&mut self, prio: Priority) -> ControlFlow<A> {
|
||||||
|
if let Some((_, task, _)) = &mut self.task.as_mut().filter(|(_, _, p)| *p <= prio) {
|
||||||
|
match Pin::new(task).resume(self.data.clone()) {
|
||||||
|
GeneratorState::Yielded(action) => ControlFlow::Break(action),
|
||||||
|
GeneratorState::Complete(_) => {
|
||||||
|
self.task = None;
|
||||||
|
ControlFlow::Continue(())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ControlFlow::Continue(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn perform<T: Generator<Data<C>, Yield = A, Return = ()> + Unpin + Any + Send + Sync>(
|
||||||
|
&mut self,
|
||||||
|
prio: Priority,
|
||||||
|
task: T,
|
||||||
|
) -> ControlFlow<A> {
|
||||||
|
let ty = TypeId::of::<T>();
|
||||||
|
if self.task.as_mut().filter(|(ty1, _, _)| *ty1 == ty).is_none() {
|
||||||
|
self.task = Some((ty, Box::new(task), prio));
|
||||||
|
};
|
||||||
|
|
||||||
|
self.finish(prio)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Brain<C: Context, A = ()> {
|
||||||
|
task: Box<dyn Generator<Data<C>, Yield = A, Return = !> + Unpin + Send + Sync>,
|
||||||
|
data: Data<C>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: Context, A> Brain<C, A> {
|
||||||
|
pub fn new<T: Generator<Data<C>, Yield = A, Return = !> + Unpin + Any + Send + Sync>(task: T) -> Self {
|
||||||
|
Self {
|
||||||
|
task: Box::new(task),
|
||||||
|
data: Data(Arc::new(AtomicPtr::new(std::ptr::null_mut())), PhantomData),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tick(
|
||||||
|
&mut self,
|
||||||
|
ctx_ref: &mut C::Ty<'_>,
|
||||||
|
) -> A {
|
||||||
|
self.data.0.store(ctx_ref as *mut C::Ty<'_> as *mut (), Ordering::SeqCst);
|
||||||
|
match Pin::new(&mut self.task).resume(self.data.clone()) {
|
||||||
|
GeneratorState::Yielded(action) => {
|
||||||
|
self.data.0.store(std::ptr::null_mut(), Ordering::Release);
|
||||||
|
action
|
||||||
|
},
|
||||||
|
GeneratorState::Complete(ret) => match ret {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct Npc {
|
pub struct Npc {
|
||||||
// Persisted state
|
// Persisted state
|
||||||
@ -181,6 +289,9 @@ pub struct Npc {
|
|||||||
|
|
||||||
#[serde(skip_serializing, skip_deserializing)]
|
#[serde(skip_serializing, skip_deserializing)]
|
||||||
pub task_state: Option<TaskState>,
|
pub task_state: Option<TaskState>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing, skip_deserializing)]
|
||||||
|
pub brain: Option<Brain<npc_ai::NpcData<'static>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for Npc {
|
impl Clone for Npc {
|
||||||
@ -196,6 +307,7 @@ impl Clone for Npc {
|
|||||||
goto: Default::default(),
|
goto: Default::default(),
|
||||||
mode: Default::default(),
|
mode: Default::default(),
|
||||||
task_state: Default::default(),
|
task_state: Default::default(),
|
||||||
|
brain: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -215,6 +327,7 @@ impl Npc {
|
|||||||
goto: None,
|
goto: None,
|
||||||
mode: NpcMode::Simulated,
|
mode: NpcMode::Simulated,
|
||||||
task_state: Default::default(),
|
task_state: Default::default(),
|
||||||
|
brain: Some(npc_ai::brain()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,19 +65,19 @@ impl Data {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Spawn some test entities at the sites
|
// Spawn some test entities at the sites
|
||||||
for (site_id, site) in this.sites.iter() {
|
for (site_id, site) in this.sites.iter().take(1) {
|
||||||
let rand_wpos = |rng: &mut SmallRng| {
|
let rand_wpos = |rng: &mut SmallRng| {
|
||||||
let wpos2d = site.wpos.map(|e| e + rng.gen_range(-10..10));
|
let wpos2d = site.wpos.map(|e| e + rng.gen_range(-10..10));
|
||||||
wpos2d
|
wpos2d
|
||||||
.map(|e| e as f32 + 0.5)
|
.map(|e| e as f32 + 0.5)
|
||||||
.with_z(world.sim().get_alt_approx(wpos2d).unwrap_or(0.0))
|
.with_z(world.sim().get_alt_approx(wpos2d).unwrap_or(0.0))
|
||||||
};
|
};
|
||||||
for _ in 0..20 {
|
for _ in 0..1 {
|
||||||
this.npcs.create(
|
this.npcs.create(
|
||||||
Npc::new(rng.gen(), rand_wpos(&mut rng))
|
Npc::new(rng.gen(), rand_wpos(&mut rng))
|
||||||
.with_faction(site.faction)
|
.with_faction(site.faction)
|
||||||
.with_home(site_id)
|
.with_home(site_id)
|
||||||
.with_profession(match rng.gen_range(0..20) {
|
.with_profession(match 1/*rng.gen_range(0..20)*/ {
|
||||||
0 => Profession::Hunter,
|
0 => Profession::Hunter,
|
||||||
1 => Profession::Blacksmith,
|
1 => Profession::Blacksmith,
|
||||||
2 => Profession::Chef,
|
2 => Profession::Chef,
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
#![feature(
|
#![feature(
|
||||||
explicit_generic_args_with_impl_trait,
|
|
||||||
generic_associated_types,
|
generic_associated_types,
|
||||||
never_type,
|
never_type,
|
||||||
try_blocks
|
try_blocks,
|
||||||
|
generator_trait,
|
||||||
|
generators,
|
||||||
|
trait_alias
|
||||||
)]
|
)]
|
||||||
|
|
||||||
pub mod data;
|
pub mod data;
|
||||||
|
@ -2,7 +2,7 @@ use std::{collections::VecDeque, hash::BuildHasherDefault};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
data::{
|
data::{
|
||||||
npc::{Controller, Npc, NpcId, PathData, PathingMemory, Task, TaskState, CONTINUE, FINISH},
|
npc::{Controller, Npc, NpcId, PathData, PathingMemory, Task, TaskState, CONTINUE, FINISH, TaskBox, Brain, Data, Context},
|
||||||
Sites,
|
Sites,
|
||||||
},
|
},
|
||||||
event::OnTick,
|
event::OnTick,
|
||||||
@ -241,6 +241,10 @@ impl Rule for NpcAi {
|
|||||||
.task_state
|
.task_state
|
||||||
.take()
|
.take()
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
let mut brain = ctx.state.data_mut().npcs[npc_id]
|
||||||
|
.brain
|
||||||
|
.take()
|
||||||
|
.unwrap_or_else(brain);
|
||||||
|
|
||||||
let (controller, task_state) = {
|
let (controller, task_state) = {
|
||||||
let data = &*ctx.state.data();
|
let data = &*ctx.state.data();
|
||||||
@ -295,6 +299,13 @@ impl Rule for NpcAi {
|
|||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
brain.tick(&mut NpcData {
|
||||||
|
ctx: &ctx,
|
||||||
|
npc,
|
||||||
|
npc_id,
|
||||||
|
controller: &mut controller,
|
||||||
|
});
|
||||||
|
/*
|
||||||
// // Choose a random plaza in the npcs home site (which should be the
|
// // Choose a random plaza in the npcs home site (which should be the
|
||||||
// // current here) to go to.
|
// // current here) to go to.
|
||||||
let task =
|
let task =
|
||||||
@ -327,6 +338,7 @@ impl Rule for NpcAi {
|
|||||||
.repeat();
|
.repeat();
|
||||||
|
|
||||||
task_state.perform(task, &(npc_id, &*npc, &ctx), &mut controller)?;
|
task_state.perform(task, &(npc_id, &*npc, &ctx), &mut controller)?;
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -335,6 +347,7 @@ impl Rule for NpcAi {
|
|||||||
|
|
||||||
ctx.state.data_mut().npcs[npc_id].goto = controller.goto;
|
ctx.state.data_mut().npcs[npc_id].goto = controller.goto;
|
||||||
ctx.state.data_mut().npcs[npc_id].task_state = Some(task_state);
|
ctx.state.data_mut().npcs[npc_id].task_state = Some(task_state);
|
||||||
|
ctx.state.data_mut().npcs[npc_id].brain = Some(brain);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -599,3 +612,133 @@ impl Task for TravelTo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
let data = ctx.state.data();
|
||||||
|
let site2 =
|
||||||
|
npc.home.and_then(|home| data.sites.get(home)).and_then(
|
||||||
|
|home| match &ctx.index.sites.get(home.world_site?).kind
|
||||||
|
{
|
||||||
|
SiteKind::Refactor(site2)
|
||||||
|
| SiteKind::CliffTown(site2)
|
||||||
|
| SiteKind::DesertCity(site2) => Some(site2),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let wpos = site2
|
||||||
|
.and_then(|site2| {
|
||||||
|
let plaza = &site2.plots
|
||||||
|
[site2.plazas().choose(&mut thread_rng())?];
|
||||||
|
Some(site2.tile_center_wpos(plaza.root_tile()).as_())
|
||||||
|
})
|
||||||
|
.unwrap_or(npc.wpos.xy());
|
||||||
|
|
||||||
|
TravelTo {
|
||||||
|
wpos,
|
||||||
|
use_paths: true,
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
trait IsTask = core::ops::Generator<Data<NpcData<'static>>, Yield = (), Return = ()> + Any + Send + Sync;
|
||||||
|
|
||||||
|
pub struct NpcData<'a> {
|
||||||
|
ctx: &'a EventCtx<'a, NpcAi, OnTick>,
|
||||||
|
npc_id: NpcId,
|
||||||
|
npc: &'a Npc,
|
||||||
|
controller: &'a mut Controller,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Context for NpcData<'static> {
|
||||||
|
type Ty<'a> = NpcData<'a>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn brain() -> Brain<NpcData<'static>> {
|
||||||
|
Brain::new(|mut data: Data<NpcData>| {
|
||||||
|
let mut task = TaskBox::<_, ()>::new(data.clone());
|
||||||
|
|
||||||
|
loop {
|
||||||
|
println!("Started");
|
||||||
|
while let ControlFlow::Break(end) = task.finish(0) {
|
||||||
|
yield end;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Choose a new plaza in the NPC's home site to path towards
|
||||||
|
let path = data.with(|d| {
|
||||||
|
let data = d.ctx.state.data();
|
||||||
|
|
||||||
|
let current_site = data.sites.get(d.npc.current_site?)?;
|
||||||
|
let site2 = match &d.ctx.index.sites.get(current_site.world_site?).kind {
|
||||||
|
SiteKind::Refactor(site2)
|
||||||
|
| SiteKind::CliffTown(site2)
|
||||||
|
| SiteKind::DesertCity(site2) => Some(site2),
|
||||||
|
_ => None,
|
||||||
|
}?;
|
||||||
|
|
||||||
|
let plaza = &site2.plots[site2.plazas().choose(&mut thread_rng())?];
|
||||||
|
let end_wpos = site2.tile_center_wpos(plaza.root_tile());
|
||||||
|
|
||||||
|
if end_wpos.as_::<f32>().distance(d.npc.wpos.xy()) < 32.0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let start = site2.wpos_tile_pos(d.npc.wpos.xy().as_());
|
||||||
|
let end = site2.wpos_tile_pos(plaza.root_tile());
|
||||||
|
|
||||||
|
let path = match path_in_site(start, end, site2) {
|
||||||
|
PathResult::Path(path) => path,
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
println!("CHOSE PATH, len = {}, start = {:?}, end = {:?}\nnpc = {:?}", path.len(), start, end, d.npc_id);
|
||||||
|
Some((current_site.world_site?, path))
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some((site, path)) = path {
|
||||||
|
println!("Begin path");
|
||||||
|
task.perform(0, walk_path(site, path));
|
||||||
|
} else {
|
||||||
|
println!("No path, waiting...");
|
||||||
|
for _ in 0..100 {
|
||||||
|
yield ();
|
||||||
|
}
|
||||||
|
println!("Waited.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn walk_path(site: Id<WorldSite>, path: Path<Vec2<i32>>) -> impl IsTask {
|
||||||
|
move |mut data: Data<NpcData>| {
|
||||||
|
for tile in path {
|
||||||
|
println!("TILE");
|
||||||
|
let wpos = data.with(|d| match &d.ctx.index.sites.get(site).kind {
|
||||||
|
SiteKind::Refactor(site2)
|
||||||
|
| SiteKind::CliffTown(site2)
|
||||||
|
| SiteKind::DesertCity(site2) => Some(site2),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
.expect("intrasite path should only be started on a site2 site")
|
||||||
|
.tile_center_wpos(tile)
|
||||||
|
.as_()
|
||||||
|
+ 0.5);
|
||||||
|
|
||||||
|
println!("Walking to next tile... tile wpos = {:?} npc wpos = {:?}", wpos, data.with(|d| d.npc.wpos));
|
||||||
|
while data.with(|d| d.npc.wpos.xy().distance_squared(wpos) > 2.0) {
|
||||||
|
data.with(|d| d.controller.goto = Some((
|
||||||
|
wpos.with_z(d.ctx.world
|
||||||
|
.sim()
|
||||||
|
.get_alt_approx(wpos.map(|e| e as i32))
|
||||||
|
.unwrap_or(0.0)),
|
||||||
|
1.0,
|
||||||
|
)));
|
||||||
|
yield ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Waiting..");
|
||||||
|
for _ in 0..100 {
|
||||||
|
yield ();
|
||||||
|
}
|
||||||
|
println!("Waited.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user