MGR: First round of changes for asycn GUI RPCs; includes temporary changes for testing

svn path=/workspaces/charlief/; revision=15633
This commit is contained in:
Charlie Fenton 2008-07-18 14:38:27 +00:00
parent 6e64b3d350
commit f3d4907bee
7 changed files with 627 additions and 1 deletions

View File

@ -5845,3 +5845,12 @@ David 17 July 2008
sched/
sched_send.C
Charlie 18 July 2008
- MGR: First round of changes for asycn GUI RPCs; includes temporary
changes for testing.
clientgui/
AsyncRPC.cpp,.h
BOINCBaseFrame.cpp,.h
MainDocument.cpp,.h

407
clientgui/AsyncRPC.cpp Normal file
View File

@ -0,0 +1,407 @@
// Berkeley Open Infrastructure for Network Computing
// http://boinc.berkeley.edu
// Copyright (C) 2005 University of California
//
// This is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation;
// either version 2.1 of the License, or (at your option) any later version.
//
// This software is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Lesser General Public License for more details.
//
// To view the GNU Lesser General Public License visit
// http://www.gnu.org/copyleft/lesser.html
// or write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#if defined(__GNUG__) && !defined(__APPLE__)
#pragma implementation "AsyncRPC.h"
#endif
#include <vector>
#include "stdwx.h"
//#include "wx/artprov.h"
#include "BOINCGUIApp.h"
#include "MainDocument.h"
#include "AsyncRPC.h"
//#include "BOINCClientManager.h"
// Delay in milliseconds before showing AsyncRPCDlg
#define RPC_WAIT_DLG_DELAY 250
ASYNC_RPC_REQUEST::ASYNC_RPC_REQUEST() {
clear();
}
ASYNC_RPC_REQUEST::~ASYNC_RPC_REQUEST() {
clear();
}
void ASYNC_RPC_REQUEST::clear() {
which_rpc = (RPC_SELECTOR) 0;
inBuf = NULL;
exchangeBuf = NULL;
outBuf = NULL;
event = NULL;
eventHandler = NULL;
completionTime = NULL;
isActive = false;
}
bool ASYNC_RPC_REQUEST::isSameAs(ASYNC_RPC_REQUEST& otherRequest) {
if (which_rpc != otherRequest.which_rpc) return false;
if (inBuf != otherRequest.inBuf) return false;
if (exchangeBuf != otherRequest.exchangeBuf) return false;
if (outBuf != otherRequest.outBuf) return false;
if (event != otherRequest.event) {
if (event->GetEventType() != (otherRequest.event)->GetEventType()) return false;
if (event->GetId() != (otherRequest.event)->GetId()) return false;
if (event->GetEventObject() != (otherRequest.event)->GetEventObject()) return false;
}
if (eventHandler != otherRequest.eventHandler) return false;
if (completionTime != otherRequest.completionTime) return false;
// OK if isActive doesn't match.
return true;
}
RPCThread::RPCThread(CMainDocument *pDoc)
: wxThread() {
m_Doc = pDoc;
}
void RPCThread::OnExit() {
}
// We don't need critical sections because:
// 1. CMainDocument never modifies mDoc->current_rpc_request while the
// async RPC thread is using it.
// 2. The async RPC thread never modifies either mDoc->current_rpc_request
// or the vector of requests mDoc->RPC_requests.
void *RPCThread::Entry() {
int retval;
while(true) {
// check if we were asked to exit
if ( TestDestroy() )
break;
if (! m_Doc->GetCurrentRPCRequest()->isActive) {
// Wait until CMainDocument issues next RPC request
Yield();
continue;
}
retval = ProcessRPCRequest();
// TODO: Do we need a critical section here and in CBOINCGUIApp::SetActiveGUI()?
// We need to get the frame each time, in case it was
// changed by a call to CBOINCGUIApp::SetActiveGUI().
CBOINCBaseFrame* pFrame = wxGetApp().GetFrame();
wxASSERT(wxDynamicCast(pFrame, CBOINCBaseFrame));
CRPCFinishedEvent RPC_done_event( wxEVT_RPC_FINISHED, pFrame );
RPC_done_event.SetInt(retval);
wxPostEvent( pFrame, RPC_done_event );
}
return NULL;
}
int RPCThread::ProcessRPCRequest() {
int retval = 0;
ASYNC_RPC_REQUEST *current_request;
current_request = m_Doc->GetCurrentRPCRequest();
switch (current_request->which_rpc) {
case RPC_GET_RESULTS:
if (current_request->inBuf == NULL) return -1;
// TODO: Confirm if the following is correct
retval = (m_Doc->rpc).get_results((RESULTS&)*(RESULTS*)(current_request->inBuf));
break;
case RPC_GET_ALL_PROJECTS_LIST:
if (current_request->inBuf == NULL) return -1;
// Sleep(5000); // TEMPORARY FOR TESTING ASYNC RPCs -- CAF
retval = (m_Doc->rpc).get_all_projects_list((ALL_PROJECTS_LIST&)*(ALL_PROJECTS_LIST*)(current_request->inBuf));
break;
default:
break;
}
#if USE_CRITICAL_SECTIONS_FOR_ASYNC_RPCS
m_Doc->m_critsect.Enter();
current_request->isActive = false;
m_Doc->m_critsect.Leave();
#else
// Deactivation is an atomic operation
current_request->isActive = false;
#endif
return retval;
}
// We don't need critical sections because:
// 1. CMainDocument never modifies mDoc->current_rpc_request while the
// async RPC thread is using it.
// 2. The async RPC thread never modifies either mDoc->current_rpc_request
// or the vector of requests mDoc->RPC_requests.
// TODO: combine RPC requests for different buffers, then just copy the buffer.
int CMainDocument::RequestRPC(ASYNC_RPC_REQUEST& request) {
static bool inUserRequest = false;
std::vector<ASYNC_RPC_REQUEST>::iterator iter;
int retval = 0;
// Check if a duplicate request is already on the queue
for (iter=RPC_requests.begin(); iter!=RPC_requests.end(); iter++) {
if (iter->isSameAs(request)) return 0;
}
if (request.event == NULL) {
// If no completion event specified, this is a user-initiated event.
// Since the user is waiting, insert this at head of request queue
iter = RPC_requests.insert(RPC_requests.begin(), request);
} else {
RPC_requests.push_back(request);
}
// Start this RPC if no other RPC is already in progress.
if (RPC_requests.size() == 1) {
#if USE_CRITICAL_SECTIONS_FOR_ASYNC_RPCS
m_critsect.Enter();
current_rpc_request = request;
current_rpc_request.isActive = true;
m_critsect.Leave();
#else
// Make sure activation is an atomic operation
request.isActive = false;
current_rpc_request = request;
current_rpc_request.isActive = true;
#endif
}
// If no completion event specified, this is a user-initiated event so
// wait for completion but show a dialog allowing the user to cancel.
if ((request.event == NULL) && !inUserRequest) {
inUserRequest = true;
// Don't show dialog if RPC completes before RPC_WAIT_DLG_DELAY
wxStopWatch Dlgdelay = wxStopWatch();
m_RPCWaitDlg = new AsyncRPCDlg();
do {
// OnRPCComplete() sets m_RPCWaitDlg to NULL if RPC completed
if (! m_RPCWaitDlg) {
inUserRequest = false;
return retval;
}
::wxSafeYield(NULL, true); // Continue processing events
} while (Dlgdelay.Time() < RPC_WAIT_DLG_DELAY);
// TODO: ****** Cancel RPC if user pressed Cancel. ********
// TODO: ****** Because buffer may have been on stack! ********
if (m_RPCWaitDlg) {
// GetCurrentProcess(&psn);
// SetFrontProcess(&psn); // Shows process if hidden
if (m_RPCWaitDlg->ShowModal() != wxID_OK) retval = -1;
if (m_RPCWaitDlg)
m_RPCWaitDlg->Destroy();
m_RPCWaitDlg = NULL;
}
inUserRequest = false;
}
return retval;
}
void CMainDocument::OnRPCComplete(CRPCFinishedEvent& event) {
int retval = event.GetInt();
int i, n;
std::vector<ASYNC_RPC_REQUEST> completed_RPC_requests;
// Move all requests for the completed RPC to our local vector
// We do this in reverse order so we can remove them from queue
n = RPC_requests.size();
for (i=n-1; i>=0; --i) {
if (RPC_requests[i].which_rpc == current_rpc_request.which_rpc) {
completed_RPC_requests.push_back(RPC_requests[i]);
RPC_requests[i].event = NULL; // Is this needed to prevent calling event's destructor?
RPC_requests.erase(RPC_requests.begin()+i);
}
}
current_rpc_request.clear();
// Start the next RPC request while we process the one just completed.
if (RPC_requests.size() > 0) {
#if USE_CRITICAL_SECTIONS_FOR_ASYNC_RPCS
m_critsect.Enter();
current_rpc_request = RPC_requests[0];
current_rpc_request.isActive = true;
m_critsect.Leave();
#else
// Make sure activation is an atomic operation
RPC_requests[0].isActive = false;
current_rpc_request = RPC_requests[0];
current_rpc_request.isActive = true;
#endif
}
// Now process the requests we have satisfied.
n = completed_RPC_requests.size();
for (i=n-1; i>=0; --i) {
if (completed_RPC_requests[i].completionTime) {
*(completed_RPC_requests[i].completionTime) = wxDateTime::Now();
}
switch (completed_RPC_requests[i].which_rpc) {
case RPC_GET_RESULTS:
m_iGet_results_RPC_retval = retval;
if (completed_RPC_requests[i].exchangeBuf) {
RESULTS* inBuf = (RESULTS*)completed_RPC_requests[i].inBuf;
RESULTS* exchangeBuf = (RESULTS*)completed_RPC_requests[i].exchangeBuf;
inBuf->results.swap(exchangeBuf->results);
}
break;
case RPC_GET_ALL_PROJECTS_LIST:
m_iGet_results_RPC_retval = retval;
if (completed_RPC_requests[i].exchangeBuf) {
ALL_PROJECTS_LIST* inBuf = (ALL_PROJECTS_LIST*)completed_RPC_requests[i].inBuf;
ALL_PROJECTS_LIST* exchangeBuf = (ALL_PROJECTS_LIST*)completed_RPC_requests[i].exchangeBuf;
inBuf->projects.swap(exchangeBuf->projects);
}
break;
default:
break;
}
if (completed_RPC_requests[i].event) {
if (completed_RPC_requests[i].eventHandler) {
completed_RPC_requests[i].eventHandler->ProcessEvent(*completed_RPC_requests[i].event);
} else {
CBOINCBaseFrame* pFrame = wxGetApp().GetFrame();
wxASSERT(wxDynamicCast(pFrame, CBOINCBaseFrame));
pFrame->ProcessEvent(*completed_RPC_requests[i].event);
}
delete completed_RPC_requests[i].event;
completed_RPC_requests[i].event = NULL;
}
}
if (m_RPCWaitDlg) {
if (m_RPCWaitDlg->IsShown()) {
m_RPCWaitDlg->EndModal(wxID_OK);
} else {
m_RPCWaitDlg->Destroy();
m_RPCWaitDlg = NULL;
}
}
}
IMPLEMENT_CLASS(AsyncRPCDlg, wxDialog)
AsyncRPCDlg::AsyncRPCDlg() : wxDialog( NULL, wxID_ANY, wxT(""), wxDefaultPosition ) {
wxString message = wxString(_("Communicating with BOINC client. Please wait ..."));
wxBoxSizer *topsizer = new wxBoxSizer( wxVERTICAL );
wxBoxSizer *icon_text = new wxBoxSizer( wxHORIZONTAL );
// wxBitmap bitmap = wxArtProvider::GetIcon(wxART_INFORMATION, wxART_MESSAGE_BOX);
// wxStaticBitmap *icon = new wxStaticBitmap(this, wxID_ANY, bitmap);
// icon_text->Add( icon, 0, wxCENTER );
icon_text->Add( CreateTextSizer( message ), 0, wxALIGN_CENTER | wxLEFT, 10 );
topsizer->Add( icon_text, 1, wxCENTER | wxLEFT|wxRIGHT|wxTOP, 10 );
int center_flag = wxEXPAND;
wxSizer *sizerBtn = CreateSeparatedButtonSizer(wxCANCEL|wxNO_DEFAULT);
if ( sizerBtn )
topsizer->Add(sizerBtn, 0, center_flag | wxALL, 10 );
SetAutoLayout( true );
SetSizer( topsizer );
topsizer->SetSizeHints( this );
topsizer->Fit( this );
wxSize size( GetSize() );
if (size.x < size.y*3/2)
{
size.x = size.y*3/2;
SetSize( size );
}
Centre( wxBOTH | wxCENTER_FRAME);
#if USE_RPC_DLG_TIMER
m_pDlgDelayTimer = new wxTimer(this, wxID_ANY);
wxASSERT(m_pDlgDelayTimer);
m_pDlgDelayTimer->Start(100, false);
#endif // USE_RPC_DLG_TIMER
}
#if USE_RPC_DLG_TIMER
BEGIN_EVENT_TABLE(AsyncRPCDlg, wxMessageDialog)
EVT_TIMER(wxID_ANY, AsyncRPCDlg::OnRPCDlgTimer)
END_EVENT_TABLE()
AsyncRPCDlg::~AsyncRPCDlg() {
if (m_pDlgDelayTimer) {
m_pDlgDelayTimer->Stop();
delete m_pDlgDelayTimer;
m_pDlgDelayTimer = NULL;
}
}
void AsyncRPCDlg::OnRPCDlgTimer(wxTimerEvent& WXUNUSED(event)) {
::wxWakeUpIdle();
}
#endif // USE_RPC_DLG_TIMER
/// For testing: triggered by Advanced / Options menu item.
ALL_PROJECTS_LIST pl;
void CMainDocument::TestAsyncRPC() { // TEMPORARY FOR TESTING ASYNC RPCs -- CAF
ASYNC_RPC_REQUEST request;
wxDateTime completionTime;
int retval = 0;
completionTime.ResetTime();
request.which_rpc = RPC_GET_ALL_PROJECTS_LIST;
request.inBuf = &pl;
request.exchangeBuf = NULL;
request.outBuf = NULL;
request.event = NULL;
request.eventHandler = NULL;
request.completionTime = &completionTime;
request.isActive = false;
//retval = rpc.get_all_projects_list(pl);
retval = RequestRPC(request);
wxString s = completionTime.Format("%T");
printf("Completion time = %s\n", s.c_str());
printf("RequestRPC returned %d\n", retval);
}

165
clientgui/AsyncRPC.h Normal file
View File

@ -0,0 +1,165 @@
// Berkeley Open Infrastructure for Network Computing
// http://boinc.berkeley.edu
// Copyright (C) 2005 University of California
//
// This is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation;
// either version 2.1 of the License, or (at your option) any later version.
//
// This software is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Lesser General Public License for more details.
//
// To view the GNU Lesser General Public License visit
// http://www.gnu.org/copyleft/lesser.html
// or write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#ifndef _ASYNCRPC_H_
#define _ASYNCRPC_H_
#if defined(__GNUG__) && !defined(__APPLE__)
#pragma interface "AsyncRPC.cpp"
#endif
#include "wx/thread.h"
#include "BOINCBaseFrame.h"
//#include "common_defs.h"
//#include "gui_rpc_client.h"
#define USE_RPC_DLG_TIMER 0
#define USE_CRITICAL_SECTIONS_FOR_ASYNC_RPCS 0
class CMainDocument; // Forward declaration
enum RPC_SELECTOR {
RPC_INIT_POLL = 1,
RPC_AUTHORIZE,
RPC_INIT_ASYNCH,
RPC_EXCHANGE_VERSIONS,
RPC_GET_STATE,
RPC_GET_HOST_INFO,
RPC_GET_CC_STATUS,
RPC_GET_PROJECT_STATUS,
RPC_GET_RESULTS,
RPC_GET_MESSAGES,
RPC_GET_FILE_TRANSFERS,
RPC_GET_DISK_USAGE,
RPC_GET_STATISTICS,
RPC_GET_PROXY_SETTINGS,
RPC_GET_SIMPLE_GUI_INFO,
RPC_SET_RUN_MODE,
RPC_SET_NETWORK_MODE,
RPC_RUN_BENCHMARKS,
RPC_QUIT,
RPC_SET_PROXY_SETTINGS,
RPC_PROJECT_OP,
RPC_RESULT_OP,
RPC_SHOW_GRAPHICS,
RPC_FILE_TRANSFER_OP,
RPC_ACCT_MGR_RPC,
RPC_ACCT_MGR_RPC_POLL,
RPC_GET_PROJECT_CONFIG,
RPC_GET_PROJECT_CONFIG_POLL,
RPC_ACCT_MGR_INFO,
RPC_READ_GLOBAL_PREFS_OVERRIDE,
RPC_READ_CC_CONFIG,
RPC_NETWORK_AVAILABLE,
RPC_GET_PROJECT_INIT_STATUS,
RPC_INIT,
RPC_GET_GLOBAL_PREFS_WORKING_STRUCT,
RPC_GET_GLOBAL_PREFS_OVERRIDE_STRUCT,
RPC_SET_GLOBAL_PREFS_OVERRIDE_STRUCT,
RPC_CREATE_ACCOUNT,
RPC_CREATE_ACCOUNT_POLL,
RPC_LOOKUP_ACCOUNT,
RPC_LOOKUP_ACCOUNT_POLL,
RPC_PROJECT_ATTACH,
RPC_PROJECT_ATTACH_FROM_FILE,
RPC_PROJECT_ATTACH_POLL,
RPC_GET_ALL_PROJECTS_LIST
};
struct ASYNC_RPC_REQUEST {
RPC_SELECTOR which_rpc;
void *inBuf;
void *exchangeBuf;
void *outBuf;
wxEvent *event;
wxEvtHandler *eventHandler;
wxDateTime *completionTime;
bool isActive;
ASYNC_RPC_REQUEST();
~ASYNC_RPC_REQUEST();
void clear();
bool isSameAs(ASYNC_RPC_REQUEST& otherRequest);
};
class RPCThread : public wxThread
{
public:
RPCThread(CMainDocument *pDoc);
virtual void *Entry();
virtual void OnExit();
private:
int ProcessRPCRequest();
CMainDocument *m_Doc;
};
class AsyncRPCDlg : public wxDialog
{
DECLARE_DYNAMIC_CLASS( AsyncRPCDlg )
public:
AsyncRPCDlg();
void OnRPCDlgTimer(wxTimerEvent &event);
#if USE_RPC_DLG_TIMER
~AsyncRPCDlg();
private:
wxTimer* m_pDlgDelayTimer;
DECLARE_EVENT_TABLE()
#endif // USE_RPC_DLG_TIMER
};
class CRPCFinishedEvent : public wxEvent
{
public:
CRPCFinishedEvent(wxEventType evtType, CBOINCBaseFrame *pFrame)
: wxEvent(-1, evtType)
{
SetEventObject(pFrame);
}
virtual wxEvent * Clone() const { return new CRPCFinishedEvent(*this); }
void SetInt(int i) { m_retval = i; }
int GetInt() const { return m_retval; }
private:
int m_retval;
};
BEGIN_DECLARE_EVENT_TYPES()
DECLARE_EVENT_TYPE( wxEVT_RPC_FINISHED, -1 )
END_DECLARE_EVENT_TYPES()
#define EVT_RPC_FINISHED(fn) \
DECLARE_EVENT_TABLE_ENTRY(wxEVT_RPC_FINISHED, -1, -1, (wxObjectEventFunction) (wxEventFunction) &fn, NULL),
#endif // _ASYNCRPC_H_

View File

@ -45,6 +45,7 @@ DEFINE_EVENT_TYPE(wxEVT_FRAME_INITIALIZED)
DEFINE_EVENT_TYPE(wxEVT_FRAME_REFRESHVIEW)
DEFINE_EVENT_TYPE(wxEVT_FRAME_UPDATESTATUS)
DEFINE_EVENT_TYPE(wxEVT_FRAME_RELOADSKIN)
DEFINE_EVENT_TYPE(wxEVT_RPC_FINISHED)
IMPLEMENT_DYNAMIC_CLASS(CBOINCBaseFrame, wxFrame)
@ -56,6 +57,7 @@ BEGIN_EVENT_TABLE (CBOINCBaseFrame, wxFrame)
EVT_FRAME_ALERT(CBOINCBaseFrame::OnAlert)
EVT_CLOSE(CBOINCBaseFrame::OnClose)
EVT_MENU(ID_FILECLOSEWINDOW, CBOINCBaseFrame::OnCloseWindow)
EVT_RPC_FINISHED(CBOINCBaseFrame::OnRPCFinished)
END_EVENT_TABLE ()
@ -289,6 +291,16 @@ void CBOINCBaseFrame::OnCloseWindow(wxCommandEvent& WXUNUSED(event)) {
}
void CBOINCBaseFrame::OnRPCFinished( CRPCFinishedEvent& event ) {
CMainDocument* pDoc = wxGetApp().GetDocument();
wxASSERT(pDoc);
wxASSERT(wxDynamicCast(pDoc, CMainDocument));
pDoc->OnRPCComplete(event);
}
void CBOINCBaseFrame::OnExit(wxCommandEvent& WXUNUSED(event)) {
wxLogTrace(wxT("Function Start/End"), wxT("CAdvancedFrame::OnExit - Function Begin"));

View File

@ -27,6 +27,7 @@
class CFrameEvent;
class CFrameAlertEvent;
class CBOINCDialUpManager;
class CRPCFinishedEvent;
enum FrameAlertEventType {
AlertNormal = 0,
@ -61,6 +62,7 @@ public:
virtual void OnClose( wxCloseEvent& event );
virtual void OnCloseWindow( wxCommandEvent& event );
virtual void OnExit( wxCommandEvent& event );
virtual void OnRPCFinished( CRPCFinishedEvent& event );
int GetReminderFrequency() { return m_iReminderFrequency; }
wxString GetDialupConnectionName() { return m_strNetworkDialupConnectionName; }

View File

@ -64,7 +64,7 @@ CNetworkConnection::CNetworkConnection(CMainDocument* pDocument) :
m_bUsedDefaultPassword = false;
m_iPort = GUI_RPC_PORT,
m_iReadGUIRPCAuthFailure = 0;
}
}
CNetworkConnection::~CNetworkConnection() {
@ -378,6 +378,16 @@ int CMainDocument::OnInit() {
m_pClientManager = new CBOINCClientManager();
wxASSERT(m_pClientManager);
m_RPCWaitDlg = NULL;
m_RPCThread = new RPCThread(this);
wxASSERT(m_RPCThread);
iRetVal = m_RPCThread->Create();
wxASSERT(iRetVal);
m_RPCThread->Run();
return iRetVal;
}
@ -956,6 +966,7 @@ int CMainDocument::CachedResultsStatusUpdate() {
m_dtResultsTimestamp = wxDateTime::Now();
iRetVal = rpc.get_results(results);
iRetVal = m_iGet_results_RPC_retval;
if (iRetVal) {
wxLogTrace(wxT("Function Status"), wxT("CMainDocument::CachedResultsStatusUpdate - Get Result Status Failed '%d'"), iRetVal);
ForceCacheUpdate();

View File

@ -27,6 +27,7 @@
#include <vector>
#include "common_defs.h"
#include "gui_rpc_client.h"
#include "asyncRPC.h"
typedef struct {
int slot;
@ -159,6 +160,23 @@ public:
HOST_INFO host;
wxDateTime m_dtCachedStateTimestamp;
//
// Async RPC support
//
public:
int RequestRPC(ASYNC_RPC_REQUEST& request);
void OnRPCComplete(CRPCFinishedEvent& event);
ASYNC_RPC_REQUEST* GetCurrentRPCRequest() { return &current_rpc_request; };
void TestAsyncRPC(); // TEMPORARY -- CAF
#if USE_CRITICAL_SECTIONS_FOR_ASYNC_RPCS
wxCriticalSection m_critsect;
#endif
private:
RPCThread* m_RPCThread;
ASYNC_RPC_REQUEST current_rpc_request;
AsyncRPCDlg* m_RPCWaitDlg;
std::vector<ASYNC_RPC_REQUEST> RPC_requests;
//
// Project Tab
@ -210,6 +228,8 @@ private:
public:
RESULTS results;
RESULTS async_results_buf;
int m_iGet_results_RPC_retval;
RESULT* result(unsigned int);
RESULT* result(const wxString& name, const wxString& project_url);