Skip to content

Commit 83063fd

Browse files
committed
Add reload_style command to avoid daemon restart on style changes
- Add ReloadStyle command to Rust daemon for in-place style updates - Add reload_style() method to Python RenderDaemon bridge - Update Map.load_style() to use reload_style instead of daemon restart - Update Map.set_geojson() to use reload_style instead of daemon restart - Improves performance by keeping daemon warm between style changes
1 parent 14a4a38 commit 83063fd

3 files changed

Lines changed: 80 additions & 6 deletions

File tree

mlnative/_bridge.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,28 @@ def render_batch(self, views: list[dict[str, Any]]) -> list[bytes]:
185185
pngs_b64 = response.get("png", "").split(",")
186186
return [base64.b64decode(png) for png in pngs_b64 if png]
187187

188+
def reload_style(self, style: str) -> None:
189+
"""Reload the style without restarting the daemon.
190+
191+
Args:
192+
style: Style URL string or JSON string
193+
194+
Raises:
195+
MlnativeError: If style reload fails
196+
"""
197+
if not self._initialized:
198+
raise MlnativeError("Renderer not initialized")
199+
200+
cmd = {
201+
"cmd": "reload_style",
202+
"style": style,
203+
}
204+
205+
response = self._send_command(cmd)
206+
207+
if response.get("status") != "ok":
208+
raise MlnativeError(f"Style reload failed: {response.get('error')}")
209+
188210
def stop(self) -> None:
189211
"""Stop the daemon."""
190212
if self._process is not None and self._process.poll() is None:

mlnative/map.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -148,10 +148,15 @@ def load_style(self, style: str | dict[str, Any] | Path) -> None:
148148
else:
149149
raise MlnativeError(f"Style must be str, dict, or Path, got {type(style)}")
150150

151-
# Reset daemon so it picks up new style
151+
# Reload style in daemon if already running, otherwise it will pick up on next _get_daemon()
152152
if self._daemon is not None:
153-
self._daemon.stop()
154-
self._daemon = None
153+
# Get style string for daemon
154+
style_for_daemon = self._style
155+
if isinstance(style_for_daemon, dict):
156+
style_for_daemon = json.dumps(style_for_daemon)
157+
elif isinstance(style_for_daemon, Path):
158+
style_for_daemon = json.dumps(json.loads(style_for_daemon.read_text()))
159+
self._daemon.reload_style(str(style_for_daemon))
155160

156161
def render(
157162
self, center: list[float], zoom: float, bearing: float = 0, pitch: float = 0
@@ -488,10 +493,9 @@ def set_geojson(
488493
"data": geojson,
489494
}
490495

491-
# Reset daemon to pick up new style
496+
# Reload style in daemon if already running
492497
if self._daemon is not None:
493-
self._daemon.stop()
494-
self._daemon = None
498+
self._daemon.reload_style(json.dumps(self._style))
495499

496500
def close(self) -> None:
497501
"""Close the map and release resources."""

rust/src/main.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ enum Command {
1919
#[serde(default = "default_pixel_ratio")]
2020
pixel_ratio: f64,
2121
},
22+
#[serde(rename = "reload_style")]
23+
ReloadStyle { style: String },
2224
#[serde(rename = "render")]
2325
Render {
2426
center: [f64; 2],
@@ -151,6 +153,34 @@ impl Renderer {
151153
// modifying the underlying maplibre_native renderer
152154
Ok(())
153155
}
156+
157+
fn reload_style(&mut self, style: &str) -> Result<(), Box<dyn std::error::Error>> {
158+
let renderer = self
159+
.renderer
160+
.as_mut()
161+
.ok_or("Renderer not initialized")?;
162+
163+
// Check if style is URL or file path (JSON strings need to be saved to temp file)
164+
if style.starts_with("http://")
165+
|| style.starts_with("https://")
166+
|| style.starts_with("file://")
167+
{
168+
let url = style.parse().map_err(|_| "Invalid style URL")?;
169+
renderer.load_style_from_url(&url);
170+
} else if style.starts_with("{") {
171+
// JSON string - save to temp file
172+
let temp_dir = std::env::temp_dir();
173+
let temp_file = temp_dir.join(format!("mlnative_style_{}.json", std::process::id()));
174+
std::fs::write(&temp_file, style)?;
175+
renderer.load_style_from_path(&temp_file)?;
176+
// Note: temp file will be cleaned up by OS eventually
177+
} else {
178+
// Assume it's a file path
179+
renderer.load_style_from_path(style)?;
180+
}
181+
182+
Ok(())
183+
}
154184
}
155185

156186
fn main() {
@@ -237,6 +267,24 @@ fn main() {
237267
println!("{}", serde_json::to_string(&resp).unwrap());
238268
}
239269
},
270+
Command::ReloadStyle { style } => match renderer.reload_style(&style) {
271+
Ok(_) => {
272+
let resp = Response {
273+
status: "ok".to_string(),
274+
png: None,
275+
error: None,
276+
};
277+
println!("{}", serde_json::to_string(&resp).unwrap());
278+
}
279+
Err(e) => {
280+
let resp = Response {
281+
status: "error".to_string(),
282+
png: None,
283+
error: Some(format!("Reload style failed: {:?}", e)),
284+
};
285+
println!("{}", serde_json::to_string(&resp).unwrap());
286+
}
287+
},
240288
Command::RenderBatch { views } => {
241289
let mut pngs = Vec::new();
242290
let mut has_error = false;

0 commit comments

Comments
 (0)