1use axum::async_trait;
2use sea_orm::{
3 entity::prelude::*, ActiveValue::NotSet, Condition, DatabaseTransaction, IntoActiveModel, Set,
4 Unchanged,
5};
6use serde::{Deserialize, Serialize};
7
8use crate::err::{ModelError, OperationError};
9use entropy_base::grid::{navi, FlatID, Node, NodeID};
10
11use super::{node, variant::DetectedGuest};
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Deserialize)]
14#[sea_orm(table_name = "guest")]
15pub struct Model {
16 #[sea_orm(primary_key)]
17 pub id: i32,
18 pub energy: i64,
19 #[sea_orm(index)]
20 #[serde(
21 serialize_with = "entropy_base::grid::ser_flat",
22 deserialize_with = "entropy_base::grid::de_flat"
23 )]
24 pub pos: i32,
25 pub temperature: i16, #[sea_orm(index)]
27 pub master_id: i32,
28}
29
30#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
31pub enum Relation {
32 #[sea_orm(
33 belongs_to = "super::player::Entity",
34 from = "Column::MasterId",
35 to = "super::player::Column::Id"
36 )]
37 Player,
38 #[sea_orm(
39 belongs_to = "super::node::Entity",
40 from = "Column::Pos",
41 to = "super::node::Column::Id"
42 )]
43 Node,
44}
45
46impl Related<super::player::Entity> for Entity {
47 fn to() -> RelationDef {
48 Relation::Player.def()
49 }
50}
51
52impl Related<super::node::Entity> for Entity {
53 fn to() -> RelationDef {
54 Relation::Node.def()
55 }
56}
57
58#[async_trait]
59impl ActiveModelBehavior for ActiveModel {
60 async fn before_save<C>(self, db: &C, _insert: bool) -> Result<Self, DbErr>
61 where
62 C: ConnectionTrait,
63 {
64 let pos = self.pos.as_ref();
65 node::Model::_ensure(db, FlatID::from(pos.clone()))
66 .await
67 .map_err(|e| DbErr::Custom(e.to_string()))?;
68 Ok(self)
69 }
70}
71
72impl Model {
73 pub async fn spawn<C: ConnectionTrait>(
74 db: &C,
75 pos: NodeID,
76 master_id: i32,
77 ) -> Result<Model, OperationError> {
78 let g = ActiveModel {
79 id: NotSet,
80 energy: Set(0),
81 pos: Set(pos.into_i32()),
82 temperature: Set(0),
83 master_id: Set(master_id),
84 };
85 Ok(g.insert(db).await?)
86 }
87
88 pub async fn walk_free<C: ConnectionTrait>(
89 &self,
90 db: &C,
91 to: navi::Direction,
92 ) -> Result<Model, OperationError> {
93 self.verify_energy(1)?;
94
95 let at = FlatID::from(self.pos).into_node_id().navi_to(to);
96 let mut g = self.into_active_model();
97 g.pos = Set(at.into_i32());
98 g.energy = Set(self.energy - 1);
99 Ok(g.update(db).await?)
100 }
101
102 pub async fn arrange_free(
109 &self,
110 txn: &DatabaseTransaction,
111 transfer_energy: i64,
112 ) -> Result<Model, OperationError> {
113 self.consume_energy(txn, transfer_energy).await?;
114
115 let to = ActiveModel {
116 energy: Set(transfer_energy),
117 pos: Set(self.pos),
118 temperature: Set(0),
119 master_id: Set(self.master_id),
120 ..Default::default()
121 };
122
123 let to = to.insert(txn).await?;
124
125 Ok(to)
126 }
127
128 pub async fn detect<C: ConnectionTrait>(
129 &self,
130 db: &C,
131 ) -> Result<Vec<DetectedGuest>, OperationError> {
132 let gs = Entity::find()
133 .filter(
134 Condition::all()
135 .add(Column::Id.ne(self.id))
136 .add(Column::Pos.eq(self.pos)),
137 )
138 .into_partial_model::<DetectedGuest>()
139 .all(db)
140 .await?;
141 Ok(gs)
142 }
143
144 pub async fn consume_energy<C: ConnectionTrait>(
146 &self,
147 db: &C,
148 energy: i64,
149 ) -> Result<Model, OperationError> {
150 self.verify_energy(energy)?;
151
152 let mut g = self.into_active_model();
153 g.energy = Set(self.energy - energy);
154 let g: Model = g.update(db).await?;
155
156 Ok(g)
157 }
158
159 pub fn get_efficiency(&self, cell: i8) -> f32 {
160 entropy_base::heat::carnot_efficiency(self.temperature as i8, cell)
161 }
162
163 pub fn _harvest_active_model(
165 self,
166 node: Node,
167 cell_i: usize,
168 ) -> Result<(self::ActiveModel, node::ActiveModel), ModelError> {
169 let mut data = node.data.clone();
170 let mut cell = node.data.get(cell_i).ok_or(ModelError::Parse {
171 desc: format!(
172 "request length({1}) out of range <- node({0:?})",
173 node.id, cell_i
174 ),
175 })?;
176
177 let mut g = self.into_active_model();
178
179 let temp = self.temperature as i8;
181 let delta = temp.abs_diff(cell);
182 let delta = (self.get_efficiency(cell) * delta as f32).div_euclid(2.0) as u8;
183
184 if temp > cell {
189 g.temperature = Set(temp.saturating_sub_unsigned(delta) as i16);
190 cell = cell.saturating_add_unsigned(delta);
191 } else if temp < cell {
192 g.temperature = Set(temp.saturating_add_unsigned(delta) as i16);
193 cell = cell.saturating_sub_unsigned(delta);
194 } else {
195 ()
196 };
197 g.energy = Set(self.energy + delta as i64);
198 data.set(cell_i, cell);
199 let n = node::ActiveModel {
200 id: Unchanged(node.id.into_i32()),
201 data: Set(data.into()),
202 };
203 Ok((g, n))
204 }
205
206 fn verify_energy(&self, require: i64) -> Result<(), OperationError> {
211 if self.energy >= require {
212 Ok(())
213 } else {
214 Err(OperationError::EnergyNotEnough {
215 require: require,
216 reserve: self.energy,
217 })
218 }
219 }
220}