Updates to clients are sent in batches using delay.

This commit is contained in:
michael 2024-09-01 07:30:36 -07:00
parent f5a26abf77
commit 078e4ae02f
No known key found for this signature in database
GPG key ID: 4F08C3EF8040B5C7
2 changed files with 304 additions and 205 deletions

View file

@ -25,11 +25,13 @@ from codecs import lookup as codecs_lookup
from django.conf import settings
from evennia import ObjectDB
from evennia import DefaultCharacter, TASK_HANDLER
from evennia.commands.cmdhandler import cmdhandler
from evennia.commands.default.general import CmdSay
from evennia.utils.ansi import strip_mxp
from evennia.utils.logger import log_err
from evennia.utils.utils import to_str
from evennia.utils.utils import to_str, delay
from evennia.scripts.tickerhandler import TICKER_HANDLER
BrowserSessionStore = importlib.import_module(settings.SESSION_ENGINE).SessionStore
@ -44,6 +46,8 @@ _SA = object.__setattr__
_STRIP_INCOMING_MXP = settings.MXP_ENABLED and settings.MXP_OUTGOING_ONLY
_STRIP_MXP = None
_IS_TYPING_PARTICIPANTS = {}
def _NA(o):
return "N/A"
@ -142,8 +146,10 @@ def echo(session, *args, **kwargs):
"""
Echo test function
"""
if _STRIP_INCOMING_MXP:
txt = strip_mxp(txt)
# txt = kwargs.get("txt")
#
# if _STRIP_INCOMING_MXP and txt:
# txt = strip_mxp(txt)
session.data_out(text="Echo returns: %s" % args)
@ -385,8 +391,6 @@ def repeat(session, *args, **kwargs):
the above settings.
"""
from evennia.scripts.tickerhandler import TICKER_HANDLER
name = kwargs.get("callback", "")
interval = max(5, int(kwargs.get("interval", 60)))
@ -655,24 +659,104 @@ def msdp_send(session, *args, **kwargs):
session.msg(send=((), out))
def is_typing_send_update():
"""
Send relevant updates to participants
"""
participants = list(_IS_TYPING_PARTICIPANTS.keys())
for participant in participants:
if _IS_TYPING_PARTICIPANTS[participant]["session"].puppet is not None:
payload = []
# Get potentials
# TODO: exclude the speaking character
potentials = DefaultCharacter.objects.filter_family(db_location=_IS_TYPING_PARTICIPANTS[participant]["session"].puppet.location)
# if len(potentials) > 0:
for puppet in potentials:
# We're only interested in sending updates if they're capable of receiving them
if str(puppet.sessid) in participants:
payload.append({
"name": puppet.name,
"state": _IS_TYPING_PARTICIPANTS[str(puppet.sessid)]['state'],
})
_IS_TYPING_PARTICIPANTS[participant]['session'].msg(is_typing={
'type': 'typing',
'payload': payload
})
delay(5, is_typing_send_update)
else:
del _IS_TYPING_PARTICIPANTS[str(participant)]
if len(_IS_TYPING_PARTICIPANTS.keys()) > 0:
delay(5, is_typing_send_update)
def is_typing_get_aliases(session, *args, **kwargs):
"""
Used in setting up clients. Fetch list of possible "talking" triggers
Args:
session:
*args:
**kwargs:
Returns:
"""
# Add the participant to the list.
_IS_TYPING_PARTICIPANTS[str(session.sessid)] = {
"state": False,
"session": session,
}
session.msg(is_typing={'type': 'aliases', 'payload': CmdSay.aliases})
if len(_IS_TYPING_PARTICIPANTS.keys()) == 1:
delay(5, is_typing_send_update)
def is_typing_state(session, *args, **kwargs):
# audience = ObjectDB.objects.filter(db_typeclass_path="typeclasses.characters.Character",
# db_location=session.puppet.location).exclude(db_key=session.puppet.key)
audience = ObjectDB.objects.filter(db_typeclass_path="typeclasses.characters.Character",
db_location=session.puppet.location)
def is_typing_update_participant(session, *args, **kwargs):
"""
Update a participant session's typing status
for puppet in audience:
for puppet_session in puppet.sessions.all():
puppet_session.msg(is_typing={'type': 'typing',
'payload': {
'name': session.puppet.name,
'state': args[0]
}})
Args:
session:
*args: First argument is a boolean indicating their typing state
**kwargs:
Returns:
"""
# If the session isn't found then server restarted
if _IS_TYPING_PARTICIPANTS.get(session.sessid) is None:
_IS_TYPING_PARTICIPANTS[str(session.sessid)] = {
"session": session,
"state": args[0],
}
if len(_IS_TYPING_PARTICIPANTS.keys()) == 1:
delay(5, is_typing_send_update)
else:
_IS_TYPING_PARTICIPANTS[str(session.sessid)]['state'] = args[0]
def is_typing_remove_participant(session, *args, **kwargs):
"""
Handle logging out/ending a session
Args:
session:
*args:
**kwargs:
Returns:
"""
del _IS_TYPING_PARTICIPANTS[str(session.sessid)]
# client specific

View file

@ -1,209 +1,224 @@
let is_typing = (function (){
let Evennia;
// 10 second timeout
const timeout = 10 * 1000
const state = {
timeout: null,
callback: null,
is_typing: false,
typing_players: [],
cleanup_callback: null,
let is_typing = (function () {
let Evennia;
const timeout = 2 * 1000;
const state = {
timeout: null,
callback: null,
is_typing: false,
typing_players: [],
cleanup_callback: null,
};
const sayCommands = ["say"];
const createDialog = function () {
const ele = [
'<div id="istyping" class="content">',
'<div id="typingplayers"></div>',
"</div>",
].join("\n");
$("body").append(ele);
};
const playerElement = (name) =>
`<div id="istyping-${name}" class="player-is-typing">${name} is typing...</div>`;
const startedTyping = function () {
state.is_typing = true;
state.timeout = Date.now() + timeout;
state.callback = setTimeout(stoppedTyping, timeout);
sendIsTyping();
};
const stillTyping = function () {
state.timeout = Date.now() + timeout;
clearTimeout(state.callback);
state.callback = setTimeout(stoppedTyping, timeout);
};
const stoppedTyping = function () {
state.is_typing = false;
clearTimeout(state.callback);
state.callback = null;
state.timeout = null;
sendIsTyping();
};
// Make our commands array regex safe
const escapeRegExp = function (text) {
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
};
// Get the say command's aliases
const requestSayAliases = function () {
Evennia.msg("is_typing_get_aliases");
};
const setSayAliases = function (aliases) {
aliases.forEach((alias) => sayCommands.push(escapeRegExp(alias)));
};
// Update server
const sendIsTyping = function () {
Evennia.msg("is_typing_update_participant", [state.is_typing]);
};
const onLoggedIn = function () {
requestSayAliases();
};
// Listen for talk commands
const onKeydown = function (event) {
const regex = new RegExp(
`^\W*(${sayCommands.reduce((acc, cur) => acc + "|" + cur, "").substring(1)})`,
);
const inputfield = $(".inputfield:focus");
// A 'say' command is being used.
if (
Evennia.isConnected() &&
inputfield.length === 1 &&
event.key.length === 1 &&
inputfield.val().match(regex)
) {
if (event.which === 13) {
// Enter. Message sent. Reset.
stoppedTyping();
} else if (!state.is_typing) {
// Speaking just started. Set is_talking and timeout.
startedTyping();
} else if (Date.now() > state.timeout) {
// Expiration is nearing. Update timeout.
stillTyping();
}
// Not talking anymore but state hasn't been updated yet.
} else if (state.is_typing) {
stoppedTyping();
}
};
// Reset everything
const onConnectionClose = function () {
state.is_typing = false;
state.timeout = null;
state.typing_players = [];
if (state.callback) {
clearTimeout(state.callback);
}
const sayCommands = ['say']
const createDialog = function() {
const ele =[
'<div id="istyping" class="content">',
'<div id="typingplayers"></div>',
'</div>'
].join('\n')
$('body').append(ele)
if (state.cleanup_callback) {
clearInterval(state.cleanup_callback);
}
const playerElement =(name)=> `<div id="istyping-${name}" class="player-is-typing">${name} is typing...</div>`
Evennia.msg("is_typing_remove_participant");
};
const startedTyping = function () {
state.is_typing = true;
state.timeout = Date.now() + timeout;
state.callback = setTimeout(stoppedTyping, timeout);
const cleanupTimedOutPlayers = function () {
const now = Date.now();
const timedOut = [];
sendIsTyping()
state.typing_players.forEach((player, index) => {
if (player.timeout < now) {
timedOut.push(index);
$(`#istyping-${player}`).remove();
}
});
timedOut
.reverse()
.forEach((index) => state.typing_players.splice(index, 1));
if (state.typing_players.length === 0) {
clearInterval(state.cleanup_callback);
$("#istyping").hide();
}
};
const stillTyping = function () {
state.timeout = Date.now() + timeout
clearTimeout(state.callback)
state.callback = setTimeout(stoppedTyping, timeout)
const is_typing = function (args, kwargs) {
if ("type" in kwargs) {
switch (kwargs.type) {
case "aliases":
setSayAliases(kwargs.payload);
break;
sendIsTyping()
}
case "typing":
const updated_players = kwargs.payload;
let player = null;
const stoppedTyping = function () {
state.is_typing = false;
clearTimeout(state.callback);
state.callback = null;
state.timeout = null;
updated_players.forEach((updated) => {
player = state.typing_players.filter(
(player) => player.name === updated.name,
);
sendIsTyping()
}
// New talker
if (updated.state && player.length === 0) {
state.typing_players.push({
name: updated.name,
timeout: Date.now() + timeout,
});
$("#typingplayers").append(playerElement(updated.name));
// Make our commands array regex safe
const escapeRegExp = function (text) {
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
}
// Get the say command's aliases
const requestSayAliases = function () {
Evennia.msg('is_typing_get_aliases')
}
const setSayAliases = function (aliases) {
aliases.forEach(alias=>sayCommands.push(escapeRegExp(alias)))
}
// Update server
const sendIsTyping = function () {
Evennia.msg('is_typing_state', [state.is_typing])
}
const onLoggedIn = function () {
requestSayAliases();
}
// Listen for talk commands
const onKeydown = function (event) {
const regex = new RegExp(`^\W*(${sayCommands.reduce((acc, cur)=> acc + "|" + cur, "").substring(1)})`)
const inputfield = $(".inputfield:focus");
// A 'say' command is being used.
if (Evennia.isConnected() &&
inputfield.length === 1 &&
event.key.length === 1 &&
inputfield.val().match(regex)) {
// Enter. Message sent. Reset.
if (event.which === 13) {
stoppedTyping()
// Speaking just started. Set is_talking and timeout.
} else if (!state.is_typing) {
startedTyping();
// Expiration is nearing. Update timeout.
} else if (Date.now() + 5 * 1000 > state.timeout) {
stillTyping();
// Existing talker is still going
} else if (updated.state && player.length > 0) {
player[0].timeout = Date.now() + timeout;
// They're done talking
} else {
state.typing_players = state.typing_players.filter(
(player) => player.name !== updated.name,
);
$(`#istyping-${updated.name}`).remove();
}
// Not talking anymore but state hasn't been updated yet.
} else if (state.is_typing) {
stoppedTyping();
}
});
if (state.typing_players.length > 0 && !state.cleanup_callback) {
state.cleanup_callback = setInterval(cleanupTimedOutPlayers, 100);
$("#istyping").show();
} else if (
state.typing_players.length === 0 &&
state.cleanup_callback
) {
clearInterval(state.cleanup_callback);
state.cleanup_callback = null;
$("#istyping").hide();
}
break;
default:
console.log("Default case");
console.log(args);
console.log(kwargs);
}
}
};
// Reset everything
const onConnectionClose = function () {
state.is_typing = false;
state.timeout = null;
state.typing_players = []
const getState = () => state;
if (state.callback) {
clearTimeout(state.callback)
}
//
// Mandatory plugin init function
const init = function () {
let options = window.options;
options["is_typing"] = true;
Evennia = window.Evennia;
if (state.cleanup_callback) {
clearInterval(state.cleanup_callback)
}
}
Evennia.emitter.on("is_typing", is_typing);
const cleanupTimedOutPlayers = function () {
const now = Date.now();
const timedOut = []
createDialog();
state.typing_players.forEach((player, index)=>{
if (player.timeout < now) {
timedOut.push(index)
$(`#istyping-${player}`).remove()
}
})
console.log("Is Typing plugin initialized");
};
timedOut.reverse().forEach(index=>state.typing_players.splice(index, 1))
return {
init,
onLoggedIn,
onKeydown,
onConnectionClose,
getState,
};
})();
if (state.typing_players.length === 0) {
clearInterval(state.cleanup_callback)
$('#istyping').hide();
}
}
const is_typing = function (args, kwargs) {
if ('type' in kwargs) {
switch (kwargs.type) {
case 'aliases':
setSayAliases(kwargs.payload)
break;
case 'typing':
const player = state.typing_players.filter(player=>player.name === kwargs.payload.name)
// New talker
if (kwargs.payload.state &&
player.length === 0) {
state.typing_players.push({name: kwargs.payload.name, timeout: Date.now() + timeout})
$('#typingplayers').append(playerElement(kwargs.payload.name))
// Existing talker is still going
} else if (kwargs.payload.state &&
player.length > 0) {
player[0].timeout = Date.now() + timeout;
// They're done talking
} else {
state.typing_players = state.typing_players.filter(player=>player.name!== kwargs.payload.name)
$(`#istyping-${kwargs.payload.name}`).remove()
}
if (state.typing_players.length > 0 && !state.cleanup_callback) {
state.cleanup_callback = setInterval(cleanupTimedOutPlayers, 100);
$('#istyping').show();
} else if (state.typing_players.length === 0 && state.cleanup_callback) {
clearInterval(state.cleanup_callback)
state.cleanup_callback = null;
$('#istyping').hide();
}
break;
default:
console.log("Default case")
console.log(args)
console.log(kwargs)
}
}
}
const getState = () => state
//
// Mandatory plugin init function
const init = function () {
let options = window.options;
options["is_typing"] = true;
Evennia = window.Evennia;
Evennia.emitter.on("is_typing", is_typing);
createDialog();
console.log('Is Typing plugin initialized');
}
return {
init,
onLoggedIn,
onKeydown,
onConnectionClose,
getState
}
})()
window.plugin_handler.add("is_typing", is_typing)
window.plugin_handler.add("is_typing", is_typing);